Compare commits
11
Commits
3.6.0测试版1
...
cc811eddfe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc811eddfe
|
||
|
|
984031c6e2
|
||
|
|
75ff49a9d1
|
||
|
|
d6ac0da75d
|
||
|
|
455139dfe9
|
||
|
|
f0efa6d041 | ||
|
|
314f90b5f3 | ||
|
|
053450429c | ||
|
|
8dcdb08755 | ||
|
|
ba61d3b5ae | ||
|
|
74e4360d71 |
@@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
name: shadcn
|
||||||
|
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
|
||||||
|
user-invocable: false
|
||||||
|
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
||||||
|
---
|
||||||
|
|
||||||
|
# shadcn/ui
|
||||||
|
|
||||||
|
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
## Current Project Context
|
||||||
|
|
||||||
|
```json
|
||||||
|
!`npx shadcn@latest info --json`
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
|
||||||
|
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
|
||||||
|
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
|
||||||
|
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
|
||||||
|
|
||||||
|
### Styling & Tailwind → [styling.md](./rules/styling.md)
|
||||||
|
|
||||||
|
- **`className` for layout, not styling.** Never override component colors or typography.
|
||||||
|
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
||||||
|
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
|
||||||
|
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
|
||||||
|
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
|
||||||
|
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
|
||||||
|
|
||||||
|
### Forms & Inputs → [forms.md](./rules/forms.md)
|
||||||
|
|
||||||
|
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
||||||
|
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
|
||||||
|
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
|
||||||
|
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
||||||
|
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
||||||
|
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
||||||
|
|
||||||
|
### Component Structure → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
|
||||||
|
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
|
||||||
|
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||||
|
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
|
||||||
|
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
|
||||||
|
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
|
||||||
|
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
|
||||||
|
|
||||||
|
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
||||||
|
- **Callouts use `Alert`.** Don't build custom styled divs.
|
||||||
|
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
||||||
|
- **Toast follows the project base.** Use `toast` from the `toast` component for
|
||||||
|
Base UI projects. Use `toast()` from `sonner` for Radix and React Aria
|
||||||
|
projects.
|
||||||
|
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
||||||
|
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||||
|
- **Use `Badge`** instead of custom styled spans.
|
||||||
|
|
||||||
|
### Icons → [icons.md](./rules/icons.md)
|
||||||
|
|
||||||
|
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
||||||
|
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
|
||||||
|
- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
|
||||||
|
|
||||||
|
### Chat & Messaging → [chat.md](./rules/chat.md)
|
||||||
|
|
||||||
|
- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.
|
||||||
|
- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.
|
||||||
|
- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.
|
||||||
|
- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Form layout: FieldGroup + Field, not div + Label.
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
// Validation: data-invalid on Field, aria-invalid on the control.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel>Email</FieldLabel>
|
||||||
|
<Input aria-invalid />
|
||||||
|
<FieldDescription>Invalid email.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Icons in buttons: data-icon, no sizing classes.
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
// Spacing: gap-*, not space-y-*.
|
||||||
|
<div className="flex flex-col gap-4"> // correct
|
||||||
|
<div className="space-y-4"> // wrong
|
||||||
|
|
||||||
|
// Equal dimensions: size-*, not w-* h-*.
|
||||||
|
<Avatar className="size-10"> // correct
|
||||||
|
<Avatar className="w-10 h-10"> // wrong
|
||||||
|
|
||||||
|
// Status colors: Badge variants or semantic tokens, not raw colors.
|
||||||
|
<Badge variant="secondary">+20.1%</Badge> // correct
|
||||||
|
<span className="text-emerald-600">+20.1%</span> // wrong
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Selection
|
||||||
|
|
||||||
|
| Need | Use |
|
||||||
|
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
|
| Button/action | `Button` with appropriate variant |
|
||||||
|
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
|
||||||
|
| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
|
||||||
|
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
||||||
|
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
||||||
|
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
||||||
|
| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||||
|
| Command palette | `Command` inside `Dialog` |
|
||||||
|
| Charts | `Chart` (wraps Recharts) |
|
||||||
|
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
||||||
|
| Empty states | `Empty` |
|
||||||
|
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
|
||||||
|
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
|
||||||
|
| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |
|
||||||
|
|
||||||
|
## Key Fields
|
||||||
|
|
||||||
|
The injected project context contains these key fields:
|
||||||
|
|
||||||
|
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
|
||||||
|
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
|
||||||
|
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
|
||||||
|
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
|
||||||
|
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
|
||||||
|
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
|
||||||
|
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
|
||||||
|
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
|
||||||
|
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
|
||||||
|
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
|
||||||
|
- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.
|
||||||
|
|
||||||
|
See [cli.md — `info` command](./cli.md) for the full field reference.
|
||||||
|
|
||||||
|
## Component Docs, Examples, and Usage
|
||||||
|
|
||||||
|
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
```
|
||||||
|
|
||||||
|
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
|
||||||
|
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
|
||||||
|
3. **Find components** — `npx shadcn@latest search`.
|
||||||
|
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
|
||||||
|
5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
|
||||||
|
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
|
||||||
|
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
|
||||||
|
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
|
||||||
|
9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
|
||||||
|
- **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.
|
||||||
|
- **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
|
||||||
|
- **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.
|
||||||
|
- **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
|
||||||
|
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
|
||||||
|
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
|
||||||
|
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||||
|
|
||||||
|
## Updating Components
|
||||||
|
|
||||||
|
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
|
||||||
|
|
||||||
|
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
|
||||||
|
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
|
||||||
|
3. Decide per file based on the diff:
|
||||||
|
- No local changes → safe to overwrite.
|
||||||
|
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
|
||||||
|
- User says "just update everything" → use `--overwrite`, but confirm first.
|
||||||
|
4. **Never use `--overwrite` without the user's explicit approval.**
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova
|
||||||
|
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
|
||||||
|
|
||||||
|
# Create a monorepo project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --monorepo
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
|
||||||
|
|
||||||
|
# Initialize existing project.
|
||||||
|
npx shadcn@latest init --preset base-nova
|
||||||
|
npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
|
||||||
|
|
||||||
|
# Apply a preset to an existing project.
|
||||||
|
npx shadcn@latest apply a2r6bw
|
||||||
|
npx shadcn@latest apply a2r6bw --only theme
|
||||||
|
npx shadcn@latest apply a2r6bw --only font
|
||||||
|
npx shadcn@latest apply a2r6bw --only theme,font
|
||||||
|
|
||||||
|
# Inspect preset codes and project preset state.
|
||||||
|
npx shadcn@latest preset decode a2r6bw
|
||||||
|
npx shadcn@latest preset url a2r6bw
|
||||||
|
npx shadcn@latest preset open a2r6bw
|
||||||
|
npx shadcn@latest preset resolve
|
||||||
|
npx shadcn@latest preset resolve --json
|
||||||
|
|
||||||
|
# Add components.
|
||||||
|
npx shadcn@latest add button card dialog
|
||||||
|
npx shadcn@latest add @magicui/shimmer-button
|
||||||
|
npx shadcn@latest add owner/repo/item
|
||||||
|
npx shadcn@latest add --all
|
||||||
|
|
||||||
|
# Preview changes before adding/updating.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
npx shadcn@latest add @acme/form --view button.tsx
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
|
||||||
|
# Search registries.
|
||||||
|
npx shadcn@latest search @shadcn -q "sidebar"
|
||||||
|
npx shadcn@latest search @tailark -q "stats"
|
||||||
|
npx shadcn@latest search owner/repo -q "login"
|
||||||
|
npx shadcn@latest search # all configured registries
|
||||||
|
npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type
|
||||||
|
|
||||||
|
# Get component docs and example URLs.
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
|
||||||
|
# View registry item details (for items not yet installed).
|
||||||
|
npx shadcn@latest view @shadcn/button
|
||||||
|
npx shadcn@latest view owner/repo/item
|
||||||
|
```
|
||||||
|
|
||||||
|
**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
|
||||||
|
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
|
||||||
|
**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).
|
||||||
|
|
||||||
|
## Detailed References
|
||||||
|
|
||||||
|
- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
|
||||||
|
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
|
||||||
|
- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest
|
||||||
|
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
|
||||||
|
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
|
||||||
|
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
|
||||||
|
- [cli.md](./cli.md) — Commands, flags, presets, templates
|
||||||
|
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
|
||||||
|
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "shadcn/ui"
|
||||||
|
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
|
||||||
|
icon_small: "./assets/shadcn-small.png"
|
||||||
|
icon_large: "./assets/shadcn.png"
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,290 @@
|
|||||||
|
# shadcn CLI Reference
|
||||||
|
|
||||||
|
Configuration is read from `components.json`.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
|
||||||
|
- Templates: next, vite, start, react-router, astro
|
||||||
|
- Presets: named, code, URL formats and fields
|
||||||
|
- Switching presets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `init` — Initialize or create a project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest init [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
|
||||||
|
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
|
||||||
|
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `true` |
|
||||||
|
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
|
||||||
|
| `--force` | `-f` | Force overwrite existing configuration | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--name <name>` | `-n` | Name for new project | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--rtl` | | Enable RTL support | — |
|
||||||
|
| `--reinstall` | | Re-install existing UI components | `false` |
|
||||||
|
| `--monorepo` | | Scaffold a monorepo project | — |
|
||||||
|
| `--no-monorepo` | | Skip the monorepo prompt | — |
|
||||||
|
|
||||||
|
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
|
||||||
|
|
||||||
|
### `apply` — Apply a preset to an existing project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest apply [preset] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------------- | ----- | ------------------------------------------ | ------- |
|
||||||
|
| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
|
||||||
|
`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
|
||||||
|
If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
|
||||||
|
|
||||||
|
### `add` — Add components
|
||||||
|
|
||||||
|
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`),
|
||||||
|
GitHub item addresses (`owner/repo/item`), URLs, or local paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||||
|
| `--overwrite` | `-o` | Overwrite existing files | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--all` | `-a` | Add all available components | `false` |
|
||||||
|
| `--path <path>` | `-p` | Target path for the component | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--dry-run` | | Preview all changes without writing files | `false` |
|
||||||
|
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
|
||||||
|
#### Dry-Run Mode
|
||||||
|
|
||||||
|
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Preview all changes.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
|
||||||
|
# Show diffs for all files (top 5).
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
|
||||||
|
# Show the diff for a specific file.
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
|
||||||
|
# Show contents for all files (top 5).
|
||||||
|
npx shadcn@latest add button --view
|
||||||
|
|
||||||
|
# Show the full content of a specific file.
|
||||||
|
npx shadcn@latest add button --view button.tsx
|
||||||
|
|
||||||
|
# Works with URLs too.
|
||||||
|
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
|
||||||
|
|
||||||
|
# Works with public GitHub registries too.
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
|
||||||
|
# CSS diffs.
|
||||||
|
npx shadcn@latest add button --diff globals.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use dry-run:**
|
||||||
|
|
||||||
|
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
|
||||||
|
- Before overwriting existing components — use `--diff` to preview the changes first.
|
||||||
|
- When the user wants to inspect component source code without installing — use `--view`.
|
||||||
|
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
|
||||||
|
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
|
||||||
|
|
||||||
|
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
|
||||||
|
|
||||||
|
#### Smart Merge from Upstream
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
|
||||||
|
|
||||||
|
### `search` — Search registries
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest search [registries...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`.
|
||||||
|
Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`),
|
||||||
|
and registry catalog URLs. Without `-q`, lists all items. When no registries are
|
||||||
|
passed, searches every registry configured in `components.json`.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------------- | ----- | ------------------------------------------------- | ------- |
|
||||||
|
| `--query <query>` | `-q` | Search query | — |
|
||||||
|
| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |
|
||||||
|
| `--limit <number>` | `-l` | Max items to display | `100` |
|
||||||
|
| `--offset <number>` | `-o` | Items to skip | `0` |
|
||||||
|
| `--json` | | Output as JSON | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
### `view` — View item details
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest view <items...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays item info including file contents. Examples:
|
||||||
|
`npx shadcn@latest view @shadcn/button`,
|
||||||
|
`npx shadcn@latest view owner/repo/item`.
|
||||||
|
|
||||||
|
### `docs` — Get component documentation URLs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs <components...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
|
||||||
|
|
||||||
|
Example output for `npx shadcn@latest docs input button`:
|
||||||
|
|
||||||
|
```
|
||||||
|
base radix
|
||||||
|
|
||||||
|
input
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/input
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
|
||||||
|
|
||||||
|
button
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/button
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
|
||||||
|
|
||||||
|
### `diff` — Check for updates
|
||||||
|
|
||||||
|
Do not use this command. Use `npx shadcn@latest add --diff` instead.
|
||||||
|
|
||||||
|
### `info` — Project information
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest info [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------- | ----- | ----------------- | ------- |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
**Project Info fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------ |
|
||||||
|
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
|
||||||
|
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
|
||||||
|
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
|
||||||
|
| `isRSC` | `boolean` | Whether React Server Components are enabled |
|
||||||
|
| `isTsx` | `boolean` | Whether the project uses TypeScript |
|
||||||
|
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
|
||||||
|
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
|
||||||
|
| `tailwindCssFile` | `string` | Path to the global CSS file |
|
||||||
|
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
|
||||||
|
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
|
||||||
|
|
||||||
|
**Components.json fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
|
||||||
|
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
|
||||||
|
| `rsc` | `boolean` | RSC flag from config |
|
||||||
|
| `tsx` | `boolean` | TypeScript flag |
|
||||||
|
| `tailwind.config` | `string` | Tailwind config path |
|
||||||
|
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
|
||||||
|
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
|
||||||
|
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
|
||||||
|
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
|
||||||
|
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
|
||||||
|
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
|
||||||
|
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
|
||||||
|
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
|
||||||
|
| `registries` | `object` | Configured custom registries |
|
||||||
|
|
||||||
|
**Links fields:**
|
||||||
|
|
||||||
|
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
|
||||||
|
|
||||||
|
### `build` — Build a custom registry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest build [registry] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
|
||||||
|
|
||||||
|
For authoring rules, `include`, item definitions, `registryDependencies`, and
|
||||||
|
GitHub registry behavior, see [registry.md](./registry.md).
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------- | ----- | ----------------- | ------------ |
|
||||||
|
| `--output <path>` | `-o` | Output directory | `./public/r` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
| Value | Framework | Monorepo support |
|
||||||
|
| -------------- | -------------- | ---------------- |
|
||||||
|
| `next` | Next.js | Yes |
|
||||||
|
| `vite` | Vite | Yes |
|
||||||
|
| `start` | TanStack Start | Yes |
|
||||||
|
| `react-router` | React Router | Yes |
|
||||||
|
| `astro` | Astro | Yes |
|
||||||
|
| `laravel` | Laravel | No |
|
||||||
|
|
||||||
|
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presets
|
||||||
|
|
||||||
|
Three ways to specify a preset via `--preset`:
|
||||||
|
|
||||||
|
1. **Named:** `--preset nova` or `--preset lyra`
|
||||||
|
2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
|
||||||
|
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
|
||||||
|
|
||||||
|
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
|
||||||
|
> Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
|
||||||
|
|
||||||
|
## Switching Presets
|
||||||
|
|
||||||
|
Ask the user first: **overwrite**, **merge**, or **skip** existing components?
|
||||||
|
|
||||||
|
- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
|
||||||
|
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
|
||||||
|
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
|
||||||
|
|
||||||
|
Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# Customization & Theming
|
||||||
|
|
||||||
|
Components reference semantic CSS variable tokens. Change the variables to change every component.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- How it works (CSS variables → Tailwind utilities → components)
|
||||||
|
- Color variables and OKLCH format
|
||||||
|
- Dark mode setup
|
||||||
|
- Changing the theme (presets, CSS variables)
|
||||||
|
- Adding custom colors (Tailwind v3 and v4)
|
||||||
|
- Border radius
|
||||||
|
- Customizing components (variants, className, wrappers)
|
||||||
|
- Checking for updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
|
||||||
|
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
|
||||||
|
3. Components use these utilities — changing a variable changes all components that reference it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Variables
|
||||||
|
|
||||||
|
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| -------------------------------------------- | -------------------------------- |
|
||||||
|
| `--background` / `--foreground` | Page background and default text |
|
||||||
|
| `--card` / `--card-foreground` | Card surfaces |
|
||||||
|
| `--primary` / `--primary-foreground` | Primary buttons and actions |
|
||||||
|
| `--secondary` / `--secondary-foreground` | Secondary actions |
|
||||||
|
| `--muted` / `--muted-foreground` | Muted/disabled states |
|
||||||
|
| `--accent` / `--accent-foreground` | Hover and accent states |
|
||||||
|
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
|
||||||
|
| `--border` | Default border color |
|
||||||
|
| `--input` | Form input borders |
|
||||||
|
| `--ring` | Focus ring color |
|
||||||
|
| `--chart-1` through `--chart-5` | Chart/data visualization |
|
||||||
|
| `--sidebar-*` | Sidebar-specific colors |
|
||||||
|
| `--surface` / `--surface-foreground` | Secondary surface |
|
||||||
|
|
||||||
|
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ThemeProvider } from "next-themes"
|
||||||
|
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changing the Theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply a preset code from ui.shadcn.com.
|
||||||
|
npx shadcn@latest apply --preset a2r6bw
|
||||||
|
|
||||||
|
# Positional shorthand also works.
|
||||||
|
npx shadcn@latest apply a2r6bw
|
||||||
|
|
||||||
|
# Switch to a named preset and overwrite existing components.
|
||||||
|
npx shadcn@latest apply --preset nova
|
||||||
|
|
||||||
|
# Preserve existing components instead.
|
||||||
|
npx shadcn@latest init --preset nova --force --no-reinstall
|
||||||
|
|
||||||
|
# Use a custom theme URL.
|
||||||
|
npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Or edit CSS variables directly in `globals.css`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding Custom Colors
|
||||||
|
|
||||||
|
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 1. Define in the global CSS file. */
|
||||||
|
:root {
|
||||||
|
--warning: oklch(0.84 0.16 84);
|
||||||
|
--warning-foreground: oklch(0.28 0.07 46);
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--warning: oklch(0.41 0.11 46);
|
||||||
|
--warning-foreground: oklch(0.99 0.02 95);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 2a. Register with Tailwind v4 (@theme inline). */
|
||||||
|
@theme inline {
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
--color-warning-foreground: var(--warning-foreground);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 2b. Register with Tailwind v3 (tailwind.config.js).
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
warning: "oklch(var(--warning) / <alpha-value>)",
|
||||||
|
"warning-foreground":
|
||||||
|
"oklch(var(--warning-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 3. Use in components.
|
||||||
|
<div className="bg-warning text-warning-foreground">Warning</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Border Radius
|
||||||
|
|
||||||
|
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customizing Components
|
||||||
|
|
||||||
|
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
|
||||||
|
|
||||||
|
Prefer these approaches in order:
|
||||||
|
|
||||||
|
### 1. Built-in variants
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Click
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Tailwind classes via `className`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="mx-auto max-w-md">...</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add a new variant
|
||||||
|
|
||||||
|
Edit the component source to add a variant via `cva`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// components/ui/button.tsx
|
||||||
|
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Wrapper components
|
||||||
|
|
||||||
|
Compose shadcn/ui primitives into higher-level components:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export function ConfirmDialog({ title, description, onConfirm, children }) {
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checking for Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --dry-run # see all affected files
|
||||||
|
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"skill_name": "shadcn",
|
||||||
|
"evals": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
|
||||||
|
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
|
||||||
|
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
|
||||||
|
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
|
||||||
|
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
|
||||||
|
"No manual dark: color overrides"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
|
||||||
|
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Includes DialogTitle for accessibility (visible or with sr-only class)",
|
||||||
|
"Avatar component includes AvatarFallback",
|
||||||
|
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
|
||||||
|
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
|
||||||
|
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
|
||||||
|
"Uses asChild for custom triggers (radix preset)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
|
||||||
|
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
|
||||||
|
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
|
||||||
|
"Uses Badge component for percentage change instead of custom styled spans",
|
||||||
|
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
|
||||||
|
"Uses gap-* instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses size-* when width and height are equal instead of separate w-* h-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.",
|
||||||
|
"expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea",
|
||||||
|
"Wraps each row in MessageScrollerItem inside MessageScrollerContent",
|
||||||
|
"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs",
|
||||||
|
"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary",
|
||||||
|
"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card",
|
||||||
|
"Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label",
|
||||||
|
"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*",
|
||||||
|
"Includes \"use client\" when the component uses state or event handlers (isRSC)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.",
|
||||||
|
"expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring",
|
||||||
|
"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state",
|
||||||
|
"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation",
|
||||||
|
"Wraps each message row in MessageScrollerItem inside MessageScrollerContent",
|
||||||
|
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
|
||||||
|
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# shadcn MCP Server
|
||||||
|
|
||||||
|
The CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shadcn mcp # start the MCP server (stdio)
|
||||||
|
shadcn mcp init # write config for your editor
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor config files:
|
||||||
|
|
||||||
|
| Editor | Config file |
|
||||||
|
| ----------- | ------------------------------- |
|
||||||
|
| Claude Code | `.mcp.json` |
|
||||||
|
| Cursor | `.cursor/mcp.json` |
|
||||||
|
| VS Code | `.vscode/mcp.json` |
|
||||||
|
| OpenCode | `opencode.json` |
|
||||||
|
| Codex | `~/.codex/config.toml` (manual) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
|
||||||
|
|
||||||
|
### `shadcn:get_project_registries`
|
||||||
|
|
||||||
|
Returns registry names from `components.json`. Errors if no `components.json` exists.
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
### `shadcn:list_items_in_registries`
|
||||||
|
|
||||||
|
Lists all items from one or more registries. Registries can be configured
|
||||||
|
namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or
|
||||||
|
registry catalog URLs. Omit `registries` to list from every registry configured
|
||||||
|
in `components.json`.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:search_items_in_registries`
|
||||||
|
|
||||||
|
Fuzzy search across registries. Registries can be configured namespaces, public
|
||||||
|
GitHub sources, or registry catalog URLs. Omit `registries` to search every
|
||||||
|
registry configured in `components.json` — e.g. "find me a hero" across all
|
||||||
|
configured registries.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:view_items_in_registries`
|
||||||
|
|
||||||
|
View item details including full file contents.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g.
|
||||||
|
`["@shadcn/button", "@shadcn/card", "owner/repo/item"]`
|
||||||
|
|
||||||
|
### `shadcn:get_item_examples_from_registries`
|
||||||
|
|
||||||
|
Find usage examples and demos with source code. Omit `registries` to search
|
||||||
|
every registry configured in `components.json`.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
|
||||||
|
|
||||||
|
### `shadcn:get_add_command_for_items`
|
||||||
|
|
||||||
|
Returns the CLI install command.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
|
||||||
|
|
||||||
|
### `shadcn:get_audit_checklist`
|
||||||
|
|
||||||
|
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuring Registries
|
||||||
|
|
||||||
|
Namespaced and authenticated registries are set in `components.json`. The
|
||||||
|
`@shadcn` registry is always built-in. Public GitHub registries can also be used
|
||||||
|
directly as `owner/repo` registry sources when the repository has a root
|
||||||
|
`registry.json`; they do not need `components.json` configuration.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registries": {
|
||||||
|
"@acme": "https://acme.com/r/{name}.json",
|
||||||
|
"@private": {
|
||||||
|
"url": "https://private.com/r/{name}.json",
|
||||||
|
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Names must start with `@`.
|
||||||
|
- URLs must contain `{name}`.
|
||||||
|
- `${VAR}` references are resolved from environment variables.
|
||||||
|
|
||||||
|
Community registry index: `https://ui.shadcn.com/r/registries.json`
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Registry Authoring and Addresses
|
||||||
|
|
||||||
|
Use this reference when the user wants to create, fix, publish, or reason about
|
||||||
|
a shadcn registry.
|
||||||
|
|
||||||
|
## Mental Model
|
||||||
|
|
||||||
|
A registry has two forms:
|
||||||
|
|
||||||
|
- **Source registry**: an authored `registry.json` in a project or repository.
|
||||||
|
It may use `include` and file paths that point at source files.
|
||||||
|
- **Built registry**: generated JSON files served to CLI consumers, usually
|
||||||
|
from `public/r`. Use `npx shadcn@latest build` to create this form.
|
||||||
|
|
||||||
|
The CLI installer consumes registry item payloads. A source registry is a way to
|
||||||
|
author those payloads from real files.
|
||||||
|
|
||||||
|
Registry items are not limited to React components. They can distribute
|
||||||
|
components, hooks, utilities, design tokens, pages, config files, docs, rules,
|
||||||
|
workflows, templates, MCP files, and other project files.
|
||||||
|
|
||||||
|
## Root `registry.json`
|
||||||
|
|
||||||
|
The root registry file should define registry metadata and either `items` or
|
||||||
|
`include`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||||
|
"name": "acme",
|
||||||
|
"homepage": "https://acme.com",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "absolute-url",
|
||||||
|
"type": "registry:lib",
|
||||||
|
"title": "Absolute URL",
|
||||||
|
"description": "A utility to turn any path into an absolute URL.",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "lib/absolute-url.ts",
|
||||||
|
"type": "registry:lib"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Root registry rules:
|
||||||
|
|
||||||
|
- Root `registry.json` must include `name` and `homepage`.
|
||||||
|
- `items` is an array of registry item definitions.
|
||||||
|
- `include` may be used to split the source registry into multiple files.
|
||||||
|
- Included registry files may omit `name` and `homepage`.
|
||||||
|
|
||||||
|
## Include
|
||||||
|
|
||||||
|
Use `include` to keep large registries modular.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||||
|
"name": "acme",
|
||||||
|
"homepage": "https://acme.com",
|
||||||
|
"include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Include rules:
|
||||||
|
|
||||||
|
- Include paths are relative to the `registry.json` that declares them.
|
||||||
|
- Include paths must explicitly point to a `registry.json` file.
|
||||||
|
- Do not use remote URLs, absolute paths, or parent traversal (`..`).
|
||||||
|
- Item file paths are relative to the registry file that declares the item.
|
||||||
|
- Duplicate item names fail across the resolved registry.
|
||||||
|
|
||||||
|
Example included file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "button",
|
||||||
|
"type": "registry:ui",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "button.tsx",
|
||||||
|
"type": "registry:ui"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If this file is at `registry/ui/registry.json`, then `button.tsx` is read from
|
||||||
|
`registry/ui/button.tsx`, and the built item path is emitted relative to the
|
||||||
|
root registry.
|
||||||
|
|
||||||
|
## Item Definitions
|
||||||
|
|
||||||
|
Common item fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "login-form",
|
||||||
|
"type": "registry:block",
|
||||||
|
"title": "Login Form",
|
||||||
|
"description": "A login form with email and password fields.",
|
||||||
|
"dependencies": ["zod"],
|
||||||
|
"registryDependencies": ["button", "input", "label"],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "blocks/login-form.tsx",
|
||||||
|
"type": "registry:block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cssVars": {
|
||||||
|
"light": {
|
||||||
|
"brand": "oklch(0.62 0.18 250)"
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"brand": "oklch(0.72 0.16 250)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Important fields:
|
||||||
|
|
||||||
|
- `name`: the installable item name. It is not necessarily a file path.
|
||||||
|
- `type`: one of the registry item types, such as `registry:ui`,
|
||||||
|
`registry:block`, `registry:lib`, `registry:hook`, `registry:file`,
|
||||||
|
`registry:page`, `registry:theme`, `registry:style`, `registry:font`, or
|
||||||
|
`registry:item`.
|
||||||
|
- `files`: source files copied or generated by the item.
|
||||||
|
- `dependencies`: npm runtime dependencies.
|
||||||
|
- `devDependencies`: npm development dependencies.
|
||||||
|
- `registryDependencies`: other registry items required by this item.
|
||||||
|
- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time
|
||||||
|
additions.
|
||||||
|
|
||||||
|
File rules:
|
||||||
|
|
||||||
|
- File paths are relative to the declaring `registry.json`.
|
||||||
|
- `registry:file` and `registry:page` files require a `target`.
|
||||||
|
- Do not use remote file URLs in source registry file paths.
|
||||||
|
- Keep source files copy-pasteable: no hidden app-only imports.
|
||||||
|
|
||||||
|
## Registry Dependencies
|
||||||
|
|
||||||
|
`registryDependencies` entries are item addresses, not file paths.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "login-form",
|
||||||
|
"type": "registry:block",
|
||||||
|
"registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "blocks/login-form.tsx",
|
||||||
|
"type": "registry:block"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependency rules:
|
||||||
|
|
||||||
|
- Bare names such as `"button"` mean official shadcn items.
|
||||||
|
- Bare names never mean same-registry or same-repository items.
|
||||||
|
- Namespaced dependencies use `@namespace/item-name`.
|
||||||
|
- GitHub dependencies use `owner/repo/item-name`.
|
||||||
|
- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.
|
||||||
|
- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same
|
||||||
|
repo at `v2`, write `owner/repo/bar#v2`.
|
||||||
|
- Do not use relative dependencies such as `"./bar"`.
|
||||||
|
|
||||||
|
## Address Schemes
|
||||||
|
|
||||||
|
When reasoning about a registry item string, classify it first.
|
||||||
|
|
||||||
|
| Address | Scheme | Meaning |
|
||||||
|
| ----------------------------------- | --------- | ------------------------------------------------------------ |
|
||||||
|
| `button` | shadcn | Official shadcn item named `button`. |
|
||||||
|
| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |
|
||||||
|
| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |
|
||||||
|
| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |
|
||||||
|
| `./button.json` | file | Built registry item JSON on disk. |
|
||||||
|
| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |
|
||||||
|
| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |
|
||||||
|
|
||||||
|
For namespace and GitHub addresses, slashful item names are allowed and are item
|
||||||
|
names, not file paths. Addresses ending in `.json` keep file-address
|
||||||
|
precedence, so `acme/ui/data/schema.json` is treated as a file path, not a
|
||||||
|
GitHub item address.
|
||||||
|
|
||||||
|
## GitHub Registries
|
||||||
|
|
||||||
|
A public GitHub repository can act as a source registry when it has a root
|
||||||
|
`registry.json`.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
owner/repo/item-name[#ref]
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- The first two path segments are GitHub owner and repo.
|
||||||
|
- All remaining path segments are the registry item name.
|
||||||
|
- The source entrypoint is always root `registry.json`.
|
||||||
|
- GitHub registries are source registries consumed directly by the CLI. They do
|
||||||
|
not require `shadcn build` or generated item JSON files.
|
||||||
|
- `include` follows the same source-registry rules as local registries.
|
||||||
|
- Currently, GitHub addresses support public `github.com` repositories only.
|
||||||
|
- Private repos and GitHub Enterprise require explicit product decisions.
|
||||||
|
|
||||||
|
When implementing GitHub registry fetching, resolve refs to a commit SHA before
|
||||||
|
reading source files. Do not read moving refs directly from
|
||||||
|
`raw.githubusercontent.com`, because branch-like refs can be cached for several
|
||||||
|
minutes.
|
||||||
|
|
||||||
|
Preferred flow:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
owner/repo[#ref]
|
||||||
|
-> resolve ref with git ls-remote
|
||||||
|
-> commit SHA
|
||||||
|
-> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
|
||||||
|
-> read includes and item files from the same SHA
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps a command on one consistent repository snapshot.
|
||||||
|
|
||||||
|
Full 40-character commit SHAs are already stable and can be used directly.
|
||||||
|
Branches, tags, and short refs require Git so the CLI can resolve them to a
|
||||||
|
commit SHA first.
|
||||||
|
|
||||||
|
## Build and Verify
|
||||||
|
|
||||||
|
Use the CLI to build source registries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest build
|
||||||
|
npx shadcn@latest build registry.json --output public/r
|
||||||
|
```
|
||||||
|
|
||||||
|
Use CLI commands to inspect the result:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest list @acme
|
||||||
|
npx shadcn@latest search @acme -q "login"
|
||||||
|
npx shadcn@latest view @acme/login-form
|
||||||
|
npx shadcn@latest add @acme/login-form --dry-run
|
||||||
|
npx shadcn@latest registry validate ./registry.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Use GitHub addresses directly for public GitHub registries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest list owner/repo
|
||||||
|
npx shadcn@latest search owner/repo -q "login"
|
||||||
|
npx shadcn@latest view owner/repo/item
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
npx shadcn@latest registry validate owner/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
When working on registry implementation in the shadcn/ui codebase:
|
||||||
|
|
||||||
|
- Keep address parsing pure and testable.
|
||||||
|
- Do not add side effects to validators.
|
||||||
|
- Preserve existing behavior for official shadcn, namespace, URL, and file
|
||||||
|
schemes.
|
||||||
|
- Add tests for address parsing, source loading, dependency resolution, list,
|
||||||
|
search, view, and add paths.
|
||||||
|
- Prefer small source-reader abstractions over a plugin system until there are
|
||||||
|
multiple real providers.
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
# Base vs Radix
|
||||||
|
|
||||||
|
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Composition: asChild vs render
|
||||||
|
- Button / trigger as non-button element
|
||||||
|
- Select (items prop, placeholder, positioning, multiple, object values)
|
||||||
|
- ToggleGroup (type vs multiple)
|
||||||
|
- Slider (scalar vs array)
|
||||||
|
- Accordion (type and defaultValue)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Composition: asChild (radix) vs render (base)
|
||||||
|
|
||||||
|
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger>
|
||||||
|
<div>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger render={<Button />}>Open</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button / trigger as non-button element (base only)
|
||||||
|
|
||||||
|
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
|
||||||
|
|
||||||
|
**Incorrect (base):** missing `nativeButton={false}`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />}>Read the docs</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />} nativeButton={false}>
|
||||||
|
Read the docs
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button asChild>
|
||||||
|
<a href="/docs">Read the docs</a>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Same for triggers whose `render` is not a `Button`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
|
||||||
|
Pick date
|
||||||
|
</PopoverTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select
|
||||||
|
|
||||||
|
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const items = [
|
||||||
|
{ label: "Select a fruit", value: null },
|
||||||
|
{ label: "Apple", value: "apple" },
|
||||||
|
{ label: "Banana", value: "banana" },
|
||||||
|
]
|
||||||
|
|
||||||
|
<Select items={items}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
|
||||||
|
|
||||||
|
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<SelectContent alignItemWithTrigger={false} side="bottom">
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
<SelectContent position="popper">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select — multiple selection and object values (base only)
|
||||||
|
|
||||||
|
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
|
||||||
|
|
||||||
|
**Correct (base — multiple selection):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select items={items} multiple defaultValue={[]}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>
|
||||||
|
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base — object values):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>{(value) => value.name}</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ToggleGroup
|
||||||
|
|
||||||
|
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ToggleGroup type="single" defaultValue="daily">
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single (no prop needed), defaultValue is always an array.
|
||||||
|
<ToggleGroup defaultValue={["daily"]} spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup multiple>
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single, defaultValue is a string.
|
||||||
|
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup type="multiple">
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Controlled single value:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base — wrap/unwrap arrays.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
|
||||||
|
|
||||||
|
// radix — plain string.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup type="single" value={value} onValueChange={setValue}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slider
|
||||||
|
|
||||||
|
Base accepts a plain number for a single thumb. Radix always requires an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={50} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={setValue} />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accordion
|
||||||
|
|
||||||
|
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion defaultValue={["item-1"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
// Multi-select.
|
||||||
|
<Accordion multiple defaultValue={["item-1", "item-2"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
<AccordionItem value="item-2">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
# Chat & Messaging
|
||||||
|
|
||||||
|
Components for conversation and chat UI. Compose these instead of hand-rolling
|
||||||
|
bubbles, scroll containers, dividers, or attachment cards.
|
||||||
|
|
||||||
|
Install: `npx shadcn@latest add message-scroller message bubble attachment marker`
|
||||||
|
|
||||||
|
The same component names and props ship for both `base` and `radix`; only
|
||||||
|
composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Scrollable threads use MessageScroller
|
||||||
|
- Message rows use Message
|
||||||
|
- Message surfaces use Bubble
|
||||||
|
- Attachments use Attachment
|
||||||
|
- System notes and dividers use Marker
|
||||||
|
- Streaming, anchoring, and jump-to-latest are built in
|
||||||
|
- Escape hatch: the scroller hooks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scrollable threads use MessageScroller
|
||||||
|
|
||||||
|
A conversation that scrolls, follows new messages, restores position, or jumps
|
||||||
|
to a message uses `MessageScroller`. Don't build a raw overflow container with
|
||||||
|
manual scroll wiring, and don't reach for `ScrollArea`.
|
||||||
|
|
||||||
|
The parts nest in a fixed order. Every direct child of the content is wrapped in
|
||||||
|
a `MessageScrollerItem` so the scroller can measure, anchor, preserve position,
|
||||||
|
track visibility, and jump to it. `MessageScrollerButton` sits inside
|
||||||
|
`MessageScroller`, after the viewport.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Hand-rolled scroll container with manual stick-to-bottom logic.
|
||||||
|
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto">
|
||||||
|
<div className="flex flex-col gap-6 p-4">
|
||||||
|
{messages.map((m) => (
|
||||||
|
<ChatMessage key={m.id} message={m} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<MessageScrollerProvider autoScroll>
|
||||||
|
<MessageScroller>
|
||||||
|
<MessageScrollerViewport>
|
||||||
|
<MessageScrollerContent>
|
||||||
|
{messages.map((message) => (
|
||||||
|
<MessageScrollerItem
|
||||||
|
key={message.id}
|
||||||
|
messageId={message.id}
|
||||||
|
scrollAnchor={message.role === "user"}
|
||||||
|
>
|
||||||
|
<Message align={message.role === "user" ? "end" : "start"}>
|
||||||
|
{/* ...message content... */}
|
||||||
|
</Message>
|
||||||
|
</MessageScrollerItem>
|
||||||
|
))}
|
||||||
|
</MessageScrollerContent>
|
||||||
|
</MessageScrollerViewport>
|
||||||
|
<MessageScrollerButton />
|
||||||
|
</MessageScroller>
|
||||||
|
</MessageScrollerProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message rows use Message
|
||||||
|
|
||||||
|
`Message` lays out a single row: avatar, header, content, footer, with
|
||||||
|
alignment. Group consecutive rows from one sender with `MessageGroup`. Don't
|
||||||
|
rebuild the row from flex divs.
|
||||||
|
|
||||||
|
`align="end"` is the current user's side; `align="start"` is everyone else.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Message align="start">
|
||||||
|
<MessageAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src={sender.avatar} alt={sender.name} />
|
||||||
|
<AvatarFallback>{initials}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</MessageAvatar>
|
||||||
|
<MessageContent>
|
||||||
|
<MessageHeader>{sender.name}</MessageHeader>
|
||||||
|
<Bubble>
|
||||||
|
<BubbleContent>{text}</BubbleContent>
|
||||||
|
</Bubble>
|
||||||
|
<MessageFooter>{time}</MessageFooter>
|
||||||
|
</MessageContent>
|
||||||
|
</Message>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message surfaces use Bubble
|
||||||
|
|
||||||
|
The colored message surface is `Bubble` + `BubbleContent`, never a styled `div`
|
||||||
|
with `bg-muted` / `bg-primary` and hand-managed corners.
|
||||||
|
|
||||||
|
- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.
|
||||||
|
- `align`: `start` or `end` (matches the `Message` side).
|
||||||
|
|
||||||
|
`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and
|
||||||
|
`align` (`start` | `end`) position it against the bubble. Don't lay reactions out
|
||||||
|
with absolutely-positioned `Badge`s.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground">
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Bubble variant="default" align="end">
|
||||||
|
<BubbleContent>{text}</BubbleContent>
|
||||||
|
<BubbleReactions side="bottom" align="end">
|
||||||
|
<Badge variant="secondary">👍 2</Badge>
|
||||||
|
</BubbleReactions>
|
||||||
|
</Bubble>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Attachments use Attachment
|
||||||
|
|
||||||
|
File and image attachments use `Attachment`, not `Item` or a custom card. It
|
||||||
|
carries upload state, so wire `state` to the real status rather than rendering a
|
||||||
|
separate spinner.
|
||||||
|
|
||||||
|
- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and
|
||||||
|
`processing` apply the `shimmer` animation to the title automatically.
|
||||||
|
- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.
|
||||||
|
- Use `AttachmentGroup` to lay out several attachments in a scrolling row.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Attachment state="done">
|
||||||
|
<AttachmentMedia variant="icon">
|
||||||
|
<FileTextIcon />
|
||||||
|
</AttachmentMedia>
|
||||||
|
<AttachmentContent>
|
||||||
|
<AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>
|
||||||
|
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
|
||||||
|
</AttachmentContent>
|
||||||
|
<AttachmentActions>
|
||||||
|
<AttachmentAction>
|
||||||
|
<DownloadIcon />
|
||||||
|
</AttachmentAction>
|
||||||
|
</AttachmentActions>
|
||||||
|
</Attachment>
|
||||||
|
```
|
||||||
|
|
||||||
|
For an image, use `<AttachmentMedia variant="image">` with an `img` child.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System notes and dividers use Marker
|
||||||
|
|
||||||
|
Status lines ("Sarah joined the conversation"), date dividers ("Today"), and
|
||||||
|
labeled separators are `Marker`, not a `Separator` plus a centered span.
|
||||||
|
|
||||||
|
- `variant`: `default` (plain row), `separator` (centered label with rules on
|
||||||
|
each side), `border` (bottom-bordered row).
|
||||||
|
- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center gap-3 py-2">
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
<span className="text-xs text-muted-foreground">Today</span>
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Marker variant="separator">
|
||||||
|
<MarkerContent>Today</MarkerContent>
|
||||||
|
</Marker>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Streaming, anchoring, and jump-to-latest are built in
|
||||||
|
|
||||||
|
`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't
|
||||||
|
write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.
|
||||||
|
|
||||||
|
- **Follow the live edge while streaming.** `MessageScrollerProvider` with
|
||||||
|
`autoScroll` keeps the view pinned to new content and yields the moment the
|
||||||
|
user scrolls up. Streaming token updates that grow the last message are
|
||||||
|
followed automatically.
|
||||||
|
- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to
|
||||||
|
hold in view (typically the user's message that started the turn).
|
||||||
|
- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away
|
||||||
|
and scrolls back on click. `direction="end"` (default) or `direction="start"`.
|
||||||
|
It is a self-managing control, so don't gate it behind your own scroll-position
|
||||||
|
state.
|
||||||
|
|
||||||
|
For a "thinking…" indicator while the model generates, apply the `shimmer`
|
||||||
|
utility to text. Don't author a custom keyframe animation. See
|
||||||
|
[styling.md](./styling.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Escape hatch: the scroller hooks
|
||||||
|
|
||||||
|
For behavior the parts don't expose, read state from the hooks rather than
|
||||||
|
re-implementing the scroller: `useMessageScroller`,
|
||||||
|
`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come
|
||||||
|
from the auto-installed `@shadcn/react` dependency, so there's nothing extra to
|
||||||
|
install. Reach for them only when composition can't express what you need.
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Component Composition
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Items always inside their Group component
|
||||||
|
- Callouts use Alert
|
||||||
|
- Empty states use Empty component
|
||||||
|
- Toast notifications follow the project base
|
||||||
|
- Choosing between overlay components
|
||||||
|
- Dialog, Sheet, and Drawer always need a Title
|
||||||
|
- Card structure
|
||||||
|
- Button has no isPending or isLoading prop
|
||||||
|
- TabsTrigger must be inside TabsList
|
||||||
|
- Avatar always needs AvatarFallback
|
||||||
|
- Use Separator instead of raw hr or border divs
|
||||||
|
- Use Skeleton for loading placeholders
|
||||||
|
- Use Badge instead of custom styled spans
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Items always inside their Group component
|
||||||
|
|
||||||
|
Never render items directly inside the content container.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all group-based components:
|
||||||
|
|
||||||
|
| Item | Group |
|
||||||
|
|------|-------|
|
||||||
|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
|
||||||
|
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
|
||||||
|
| `MenubarItem` | `MenubarGroup` |
|
||||||
|
| `ContextMenuItem` | `ContextMenuGroup` |
|
||||||
|
| `CommandItem` | `CommandGroup` |
|
||||||
|
| `MessageScrollerItem` | `MessageScrollerContent` |
|
||||||
|
| `Message` (consecutive, same sender) | `MessageGroup` |
|
||||||
|
| `Bubble` (stacked) | `BubbleGroup` |
|
||||||
|
| `Attachment` (in a row) | `AttachmentGroup` |
|
||||||
|
|
||||||
|
Chat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Callouts use Alert
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Alert>
|
||||||
|
<AlertTitle>Warning</AlertTitle>
|
||||||
|
<AlertDescription>Something needs attention.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Empty states use Empty component
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Empty>
|
||||||
|
<EmptyHeader>
|
||||||
|
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
|
||||||
|
<EmptyTitle>No projects yet</EmptyTitle>
|
||||||
|
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
|
||||||
|
</EmptyHeader>
|
||||||
|
<EmptyContent>
|
||||||
|
<Button>Create Project</Button>
|
||||||
|
</EmptyContent>
|
||||||
|
</Empty>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Toast notifications follow the project base
|
||||||
|
|
||||||
|
For Base UI projects, use the `toast` component:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { toast } from "@/components/ui/toast"
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: "Changes saved.",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For Radix and React Aria projects, use Sonner:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
toast.success("Changes saved.")
|
||||||
|
toast.error("Something went wrong.")
|
||||||
|
toast("File deleted.", {
|
||||||
|
action: { label: "Undo", onClick: () => undoDelete() },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Choosing between overlay components
|
||||||
|
|
||||||
|
| Use case | Component |
|
||||||
|
|----------|-----------|
|
||||||
|
| Focused task that requires input | `Dialog` |
|
||||||
|
| Destructive action confirmation | `AlertDialog` |
|
||||||
|
| Side panel with details or filters | `Sheet` |
|
||||||
|
| Mobile-first bottom panel | `Drawer` |
|
||||||
|
| Quick info on hover | `HoverCard` |
|
||||||
|
| Small contextual content on click | `Popover` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dialog, Sheet, and Drawer always need a Title
|
||||||
|
|
||||||
|
`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit Profile</DialogTitle>
|
||||||
|
<DialogDescription>Update your profile.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
...
|
||||||
|
</DialogContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Card structure
|
||||||
|
|
||||||
|
Use full composition — don't dump everything into `CardContent`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Team Members</CardTitle>
|
||||||
|
<CardDescription>Manage your team.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>...</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Invite</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button has no isPending or isLoading prop
|
||||||
|
|
||||||
|
Compose with `Spinner` + `data-icon` + `disabled`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button disabled>
|
||||||
|
<Spinner data-icon="inline-start" />
|
||||||
|
Saving...
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TabsTrigger must be inside TabsList
|
||||||
|
|
||||||
|
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Tabs defaultValue="account">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="account">Account</TabsTrigger>
|
||||||
|
<TabsTrigger value="password">Password</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="account">...</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Avatar always needs AvatarFallback
|
||||||
|
|
||||||
|
Always include `AvatarFallback` for when the image fails to load:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="/avatar.png" alt="User" />
|
||||||
|
<AvatarFallback>JD</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use existing components instead of custom markup
|
||||||
|
|
||||||
|
| Instead of | Use |
|
||||||
|
|---|---|
|
||||||
|
| `<hr>` or `<div className="border-t">` | `<Separator />` |
|
||||||
|
| `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
|
||||||
|
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# Forms & Inputs
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Forms use FieldGroup + Field
|
||||||
|
- InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
- Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
- Option sets (2–7 choices) use ToggleGroup
|
||||||
|
- FieldSet + FieldLegend for grouping related fields
|
||||||
|
- Field validation and disabled states
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Forms use FieldGroup + Field
|
||||||
|
|
||||||
|
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" type="email" />
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||||||
|
<Input id="password" type="password" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
|
||||||
|
|
||||||
|
**Choosing form controls:**
|
||||||
|
|
||||||
|
- Simple text input → `Input`
|
||||||
|
- Dropdown with predefined options → `Select`
|
||||||
|
- Searchable dropdown → `Combobox`
|
||||||
|
- Native HTML select (no JS) → `native-select`
|
||||||
|
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
|
||||||
|
- Single choice from few options → `RadioGroup`
|
||||||
|
- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
|
||||||
|
- OTP/verification code → `InputOTP`
|
||||||
|
- Multi-line text → `Textarea`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
|
||||||
|
Never use raw `Input` or `Textarea` inside an `InputGroup`.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<InputGroup>
|
||||||
|
<Input placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
|
||||||
|
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="relative">
|
||||||
|
<Input placeholder="Search..." className="pr-10" />
|
||||||
|
<Button className="absolute right-0 top-0" size="icon">
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
<InputGroupAddon>
|
||||||
|
<Button size="icon">
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
</Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option sets (2–7 choices) use ToggleGroup
|
||||||
|
|
||||||
|
Don't manually loop `Button` components with active state.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const [selected, setSelected] = useState("daily")
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{["daily", "weekly", "monthly"].map((option) => (
|
||||||
|
<Button
|
||||||
|
key={option}
|
||||||
|
variant={selected === option ? "default" : "outline"}
|
||||||
|
onClick={() => setSelected(option)}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||||
|
|
||||||
|
<ToggleGroup spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Combine with `Field` for labelled toggle groups:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<FieldTitle id="theme-label">Theme</FieldTitle>
|
||||||
|
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
|
||||||
|
<ToggleGroupItem value="light">Light</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="system">System</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FieldSet + FieldLegend for grouping related fields
|
||||||
|
|
||||||
|
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldSet>
|
||||||
|
<FieldLegend variant="label">Preferences</FieldLegend>
|
||||||
|
<FieldDescription>Select all that apply.</FieldDescription>
|
||||||
|
<FieldGroup className="gap-3">
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<Checkbox id="dark" />
|
||||||
|
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</FieldSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field validation and disabled states
|
||||||
|
|
||||||
|
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Invalid.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" aria-invalid />
|
||||||
|
<FieldDescription>Invalid email address.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Disabled.
|
||||||
|
<Field data-disabled>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" disabled />
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# Icons
|
||||||
|
|
||||||
|
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Icons in Button use data-icon attribute
|
||||||
|
|
||||||
|
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="mr-2 size-4" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start"/>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button>
|
||||||
|
Next
|
||||||
|
<ArrowRightIcon data-icon="inline-end"/>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No sizing classes on icons inside components
|
||||||
|
|
||||||
|
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="size-4" data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon className="mr-2 size-4" />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pass icons as component objects, not string keys
|
||||||
|
|
||||||
|
Use `icon={CheckIcon}`, not a string key to a lookup map.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const iconMap = {
|
||||||
|
check: CheckIcon,
|
||||||
|
alert: AlertIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBadge({ icon }: { icon: string }) {
|
||||||
|
const Icon = iconMap[icon]
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon="check" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
|
||||||
|
import { CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon={CheckIcon} />
|
||||||
|
```
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Styling & Customization
|
||||||
|
|
||||||
|
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Semantic colors
|
||||||
|
- Built-in variants first
|
||||||
|
- className for layout only
|
||||||
|
- No space-x-* / space-y-*
|
||||||
|
- Prefer size-* over w-* h-* when equal
|
||||||
|
- Prefer truncate shorthand
|
||||||
|
- No manual dark: color overrides
|
||||||
|
- Use cn() for conditional classes
|
||||||
|
- No manual z-index on overlay components
|
||||||
|
- Use shimmer / scroll-fade utilities, not custom animations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic colors
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-blue-500 text-white">
|
||||||
|
<p className="text-gray-600">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-primary text-primary-foreground">
|
||||||
|
<p className="text-muted-foreground">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No raw color values for status/state indicators
|
||||||
|
|
||||||
|
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="text-emerald-600">+20.1%</span>
|
||||||
|
<span className="text-green-500">Active</span>
|
||||||
|
<span className="text-red-600">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Badge variant="secondary">+20.1%</Badge>
|
||||||
|
<Badge>Active</Badge>
|
||||||
|
<span className="text-destructive">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in variants first
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button className="border border-input bg-transparent hover:bg-accent">
|
||||||
|
Click me
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline">Click me</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## className for layout only
|
||||||
|
|
||||||
|
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="bg-blue-100 text-blue-900 font-bold">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="max-w-md mx-auto">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize a component's appearance, prefer these approaches in order:
|
||||||
|
1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
|
||||||
|
2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
|
||||||
|
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No space-x-* / space-y-*
|
||||||
|
|
||||||
|
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Input />
|
||||||
|
<Input />
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer size-* over w-* h-* when equal
|
||||||
|
|
||||||
|
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer truncate shorthand
|
||||||
|
|
||||||
|
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No manual dark: color overrides
|
||||||
|
|
||||||
|
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use cn() for conditional classes
|
||||||
|
|
||||||
|
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No manual z-index on overlay components
|
||||||
|
|
||||||
|
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use shimmer / scroll-fade utilities, not custom animations
|
||||||
|
|
||||||
|
For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.
|
||||||
|
|
||||||
|
For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]">
|
||||||
|
Thinking…
|
||||||
|
</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="shimmer">Thinking…</span>
|
||||||
|
```
|
||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-26.0.1">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="module" value="true"/>
|
<attribute name="module" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
|||||||
@@ -14,4 +14,15 @@
|
|||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1787317094049</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "KLALBMain (VS Code 自动构建)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||||
|
"projectName": "KLALB",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "KLALBMain (直接用 bin 目录)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"classPaths": [
|
||||||
|
"${workspaceFolder}/bin",
|
||||||
|
"${workspaceFolder}/src",
|
||||||
|
"${workspaceFolder}/lib/*"
|
||||||
|
],
|
||||||
|
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.runtimes": [
|
||||||
|
{
|
||||||
|
"name": "JavaSE-25",
|
||||||
|
"path": "C:\\Program Files\\Zulu\\zulu-25",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system that merges multiple WAN links into one virtual IPv6/SRv6 network. Version constant lives in `src/org/kne/cloud/network/klalb/CONST.java`. Protocol specs and manuals are the Chinese `.docx` files in the repo root.
|
||||||
|
|
||||||
|
## Build & run
|
||||||
|
|
||||||
|
No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`.
|
||||||
|
|
||||||
|
Compile (verified; `-encoding UTF-8` is mandatory — sources contain Chinese text):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& javac -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run from the repo root — CWD matters:
|
||||||
|
- reads `klalb-config.json` from CWD
|
||||||
|
- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support)
|
||||||
|
- classpath must include `src` as well as `bin`: i18n bundles (`/klalb_*.properties`) and images (`/assets/*`) are classpath resources that Eclipse copies to `bin` but manual `javac` does not
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
java --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
|
||||||
|
```
|
||||||
|
|
||||||
|
IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JDK 25. `.classpath` now references the standard container `JavaSE-25` — an execution-environment spec that any JDK ≥25 satisfies, so it works unchanged on JDK 26 machines too (the original named `jdk-26.0.1` VM broke VS Code import on machines without it). `.vscode/settings.json` maps `JavaSE-25` to the locally installed Adoptium JDK; register every installed JDK there when adding another one. Keep compiler compliance ≤25 (`.settings` pins 19) so both JDKs stay usable.
|
||||||
|
|
||||||
|
Runtime gotchas (all verified):
|
||||||
|
- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs).
|
||||||
|
- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work.
|
||||||
|
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"TUNName": null` in `klalb-config.json` or leave the TUN Name empty in GUI settings. If omitted, it defaults to `"KLALB_SRv6"`.
|
||||||
|
- Routing broadcast (`RouterInfo`) transmits `deviceName` across the network, which topology and node overview panels display. `deviceDescription` and `ExtraRoutes` remain local controller configs.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `main()` harnesses requiring real network peers. Practical check = compile succeeds + app launches.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
|
||||||
|
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
|
||||||
|
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
|
||||||
|
- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config.
|
||||||
|
- `...network.kltp` — custom reliable transport protocol (packets/streams).
|
||||||
|
- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation.
|
||||||
|
- `...network.frpc` — frp client integration.
|
||||||
|
- `...klalb.ui` — all Swing UI code.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`).
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese.
|
||||||
|
- UI strings go through `UIEnv.getRsb().getString(...)`; add keys to **both** `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
|
||||||
|
- `client.cfg`, `server.cfg`, `linetable.txt` at the root are example line-table/port-rule files loaded via the GUI file picker — not hardwired paths.
|
||||||
+55
-71
@@ -2,121 +2,105 @@
|
|||||||
{
|
{
|
||||||
"language": "ZH_CN",
|
"language": "ZH_CN",
|
||||||
"nogui": false,
|
"nogui": false,
|
||||||
"VirtualAddress": "2486:1:0:0:0:0:0:8888",
|
"VirtualAddress": "2486:1:0:0:0:0:0:8889",
|
||||||
"VirtualASN": 2142606939373348329,
|
"VirtualASN": 2142606939373348329,
|
||||||
"DNS": [
|
"DNS": [
|
||||||
"2486:1:0:0:0:0:0:8888"
|
"2486:1:0:0:0:0:0:8888"
|
||||||
],
|
],
|
||||||
"TCPListen": "0.0.0.0:4565",
|
"TCPListen": "tcp://0.0.0.0:4565",
|
||||||
"UDPListen": "{UDP}0.0.0.0:4572",
|
"UDPListen": "udp://0.0.0.0:4572",
|
||||||
"VirtualSocketName": "KLALB_Stream",
|
"VirtualSocketName": "kltp",
|
||||||
"LineTable": [
|
"LineTable": [
|
||||||
"07f4acdef99b.ofalias.net:4565"
|
"tcp://kne03.yoyo250.fun:4565",
|
||||||
|
"tcp://kne04.yoyo250.fun:4565"
|
||||||
],
|
],
|
||||||
"ConnectLineTable": [
|
"ConnectLineTable": [
|
||||||
"home-ipv6.sliveridc1.cn:4565",
|
"tcp://07f4acdef99b.ofalias.net:4565",
|
||||||
"home-ltipv6.sliveridc1.cn:4565",
|
"tcp://kne01.yoyo250.fun:4565",
|
||||||
"cn-gd-gz.sliveridc1.cn:4565"
|
"tcp://kne02.yoyo250.fun:4565"
|
||||||
],
|
],
|
||||||
"ntpServerTable": [
|
"ntpServerTable": [
|
||||||
"{UDP}ntp1.aliyun.com:123",
|
"ntp://ntp1.aliyun.com",
|
||||||
"{UDP}ntp2.aliyun.com:123",
|
"ntp://ntp2.aliyun.com",
|
||||||
"{UDP}ntp3.aliyun.com:123",
|
"ntp://ntp3.aliyun.com",
|
||||||
"{UDP}ntp4.aliyun.com:123",
|
"ntp://ntp4.aliyun.com",
|
||||||
"{UDP}ntp5.aliyun.com:123",
|
"ntp://ntp5.aliyun.com",
|
||||||
"{UDP}ntp6.aliyun.com:123",
|
"ntp://ntp6.aliyun.com",
|
||||||
"{UDP}ntp7.aliyun.com:123",
|
"ntp://ntp7.aliyun.com",
|
||||||
"{UDP}ntp1.tencent.com:123",
|
"ntp://ntp1.tencent.com",
|
||||||
"{UDP}ntp2.tencent.com:123",
|
"ntp://ntp2.tencent.com",
|
||||||
"{UDP}ntp3.tencent.com:123",
|
"ntp://ntp4.tencent.com",
|
||||||
"{UDP}ntp4.tencent.com:123",
|
"ntp://ntp5.tencent.com",
|
||||||
"{UDP}ntp5.tencent.com:123",
|
"ntp://time.google.com",
|
||||||
"{UDP}time.google.com:123",
|
"ntp://time.apple.com",
|
||||||
"{UDP}time.apple.com:123",
|
"ntp://pool.ntp.org",
|
||||||
"{UDP}pool.ntp.org:123",
|
"ntp://ntp.ntsc.ac.cn",
|
||||||
"{UDP}ntp.ntsc.ac.cn:123",
|
"ntp://us.ntp.org.cn"
|
||||||
"{UDP}us.ntp.org.cn:123"
|
|
||||||
],
|
],
|
||||||
"denyLineTableQuery": true,
|
"ExtraRoutes": [],
|
||||||
"denyLineTableBroadcast": true,
|
"denyLineTableQuery": false,
|
||||||
|
"denyLineTableBroadcast": false,
|
||||||
"congestionAlgorithm": "BBR",
|
"congestionAlgorithm": "BBR",
|
||||||
"burstLimit": 1.5,
|
"burstLimit": 2.0,
|
||||||
"delayUpperBound": 1.3,
|
"delayUpperBound": 1.2,
|
||||||
"delayLowerBound": 1.3,
|
"delayLowerBound": 1.1,
|
||||||
"nagleDelayTime": 1000000,
|
"nagleDelayTime": 1000000,
|
||||||
"linkNagleDelayTime": 0,
|
"linkNagleDelayTime": 0,
|
||||||
"linkConnectionsCount": 1,
|
"linkConnectionsCount": 1,
|
||||||
"TUNName": "KLALB_SRv6",
|
"TUNName": null,
|
||||||
|
"performanceStrategy": "multiscatter",
|
||||||
|
"DeviceName": "SerinaNya PC",
|
||||||
|
"DeviceDescription": "desc",
|
||||||
"NetworkInterfaceExcepts": [],
|
"NetworkInterfaceExcepts": [],
|
||||||
"Type": "KLALBController"
|
"Type": "KLALBController"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "0.0.0.0:4572",
|
"Listen": "kltp://[::0]:5201",
|
||||||
"Bridge": {
|
|
||||||
"KLALB": "SocketBridge",
|
|
||||||
"RDP": "SocketBridge",
|
|
||||||
"HTTP": "SocketBridge",
|
|
||||||
"HTTPS": "SocketBridge",
|
|
||||||
"DEFAULT": "MinecraftSocketBridge23332"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"KLALB": "127.0.0.1:4565",
|
|
||||||
"RDP": "127.0.0.1:3389",
|
|
||||||
"HTTP": "127.0.0.1:5212",
|
|
||||||
"HTTPS": "192.168.1.235:443",
|
|
||||||
"DEFAULT": "127.0.0.1:36555"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "{KLALB_Stream}[::0]:23332",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "MinecraftSocketBridge23332"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "127.0.0.1:36555"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "{KLALB_Stream}[::0]:23333",
|
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "SocketBridge"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:5212"
|
"DEFAULT": "tcp://127.0.0.1:5201"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "{KLALB_Stream}[::0]:25565",
|
"Listen": "tcp://[::1]:5202",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "SocketBridge"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:25566"
|
"DEFAULT": "kltp://[2486:5acd:e339:4837:a6d9:3aed:4de2:30fd]:5201"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "0.0.0.0:25561",
|
"Listen": "tcp://127.0.0.1:35000",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"KLALB": "SocketBridge",
|
"DEFAULT": "SocketBridge"
|
||||||
"DEFAULT": "MinecraftSocketBridge23330"
|
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"KLALB": "127.0.0.1:4565",
|
"DEFAULT": "kltp://[2486:1::8888]:23333"
|
||||||
"DEFAULT": "127.0.0.1:25562"
|
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "{KLALB_Stream}[::0]:23330",
|
"Listen": "kltp://[::0]:25565",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "MinecraftSocketBridge23330"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:25562"
|
"DEFAULT": "tcp://127.0.0.1:25566"
|
||||||
|
},
|
||||||
|
"Type": "SocketBridge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Listen": "tcp://127.0.0.1:35565",
|
||||||
|
"Bridge": {
|
||||||
|
"DEFAULT": "SocketBridge"
|
||||||
|
},
|
||||||
|
"Connect": {
|
||||||
|
"DEFAULT": "kltp://[2486:1::8888]:25565"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
virtualip=cefe:49b8:f837:4b00:b9cd:dbdd:c299:349
|
|
||||||
bind=0.0.0.0:4569
|
|
||||||
local=127.0.0.1:5212
|
|
||||||
@@ -3,7 +3,10 @@ networkgraph=Network graph
|
|||||||
language=Language
|
language=Language
|
||||||
basicsettings=Basic settings
|
basicsettings=Basic settings
|
||||||
ipv6addr=IPv6 address
|
ipv6addr=IPv6 address
|
||||||
|
devicename=Device name
|
||||||
|
devicedescription=Device description
|
||||||
dnsserver=DNS server
|
dnsserver=DNS server
|
||||||
|
extraroutes=Extra routes
|
||||||
asnumber=AS number
|
asnumber=AS number
|
||||||
tcplistening=TCP listening
|
tcplistening=TCP listening
|
||||||
udplistening=UDP listening
|
udplistening=UDP listening
|
||||||
@@ -34,6 +37,7 @@ saveconfigsuccess=Save config success
|
|||||||
warning=Warning
|
warning=Warning
|
||||||
invaildipv6addr=IPv6 address:Invaild Input
|
invaildipv6addr=IPv6 address:Invaild Input
|
||||||
invailddnsserver=DNS server:Invaild Input
|
invailddnsserver=DNS server:Invaild Input
|
||||||
|
invaildextraroutes=Extra routes:Invaild Input
|
||||||
invaildasnumber=AS number:Invaild Input
|
invaildasnumber=AS number:Invaild Input
|
||||||
invaildtcplisten=TCP listen:Invaild Input
|
invaildtcplisten=TCP listen:Invaild Input
|
||||||
invaildudplisten=UDP listen:Invaild Input
|
invaildudplisten=UDP listen:Invaild Input
|
||||||
@@ -105,3 +109,6 @@ uploadbasedelay=Upload Base
|
|||||||
burstlimit=Burst limit
|
burstlimit=Burst limit
|
||||||
performancesettings=Performance settings
|
performancesettings=Performance settings
|
||||||
performancestrategy=Performance strategy
|
performancestrategy=Performance strategy
|
||||||
|
singlecore=Single-Core - Cache Affinity First (Best energy/performance ratio, for low-power & cloud)
|
||||||
|
multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose physical servers)
|
||||||
|
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
|
||||||
@@ -3,7 +3,10 @@ networkgraph=网络拓扑图
|
|||||||
language=语言
|
language=语言
|
||||||
basicsettings=基本设置
|
basicsettings=基本设置
|
||||||
ipv6addr=IPv6地址
|
ipv6addr=IPv6地址
|
||||||
|
devicename=设备名称
|
||||||
|
devicedescription=设备描述
|
||||||
dnsserver=DNS服务器
|
dnsserver=DNS服务器
|
||||||
|
extraroutes=额外路由
|
||||||
asnumber=AS号码
|
asnumber=AS号码
|
||||||
tcplistening=TCP监听端口
|
tcplistening=TCP监听端口
|
||||||
udplistening=UDP监听端口
|
udplistening=UDP监听端口
|
||||||
@@ -34,6 +37,7 @@ saveconfigsuccess=保存配置成功
|
|||||||
warning=警告
|
warning=警告
|
||||||
invaildipv6addr=IPv6地址:非法输入
|
invaildipv6addr=IPv6地址:非法输入
|
||||||
invailddnsserver=DNS服务器:非法输入
|
invailddnsserver=DNS服务器:非法输入
|
||||||
|
invaildextraroutes=额外路由:非法输入
|
||||||
invaildasnumber=AS号码:非法输入
|
invaildasnumber=AS号码:非法输入
|
||||||
invaildtcplisten=TCP监听端口:非法输入
|
invaildtcplisten=TCP监听端口:非法输入
|
||||||
invaildudplisten=UDP监听端口:非法输入
|
invaildudplisten=UDP监听端口:非法输入
|
||||||
@@ -105,3 +109,6 @@ uploadbasedelay=上传延迟基线
|
|||||||
burstlimit=突发限制
|
burstlimit=突发限制
|
||||||
performancesettings=性能设置
|
performancesettings=性能设置
|
||||||
performancestrategy=性能策略
|
performancestrategy=性能策略
|
||||||
|
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
|
||||||
|
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
|
||||||
|
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
|
||||||
@@ -3,14 +3,9 @@ package org.kne.cloud.network;
|
|||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
|
|
||||||
public class DatagramSocketListener implements Closeable,AutoCloseable{
|
public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||||
private DatagramServerSocket serverSocket;
|
private DatagramServerSocket serverSocket;
|
||||||
public DatagramServerSocket getDatagramServerSocket() {
|
public DatagramServerSocket getDatagramServerSocket() {
|
||||||
@@ -36,10 +31,10 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public DatagramSocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public DatagramSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
|
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
|
||||||
@@ -49,7 +44,7 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
private void open() throws IOException {
|
private void open() throws IOException {
|
||||||
if(serverSocket==null)
|
if(serverSocket==null)
|
||||||
serverSocket=multipurposeSocketAddress.listenDatagramServerSocket();
|
serverSocket= multiProtocolSocketAddress.listenDatagramServerSocket();
|
||||||
//new Thread(r).start();
|
//new Thread(r).start();
|
||||||
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
|
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
|
||||||
}
|
}
|
||||||
@@ -63,8 +58,8 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<DatagramSocket> getCon() {
|
public Consumer<DatagramSocket> getCon() {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress> {
|
public class HostPortMap extends LinkedHashMap<String, MultiProtocolSocketAddress> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -11,9 +10,9 @@ public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress putN$H(String str) {
|
public MultiProtocolSocketAddress putN$H(String str) {
|
||||||
String[] strx=str.split("\\$");
|
String[] strx=str.split("\\$");
|
||||||
return super.put(strx[0], new MultipurposeSocketAddress( strx[1]));
|
return super.put(strx[0], new MultiProtocolSocketAddress( strx[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,8 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.*;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.MulticastSocket;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.NoRouteToHostException;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -29,12 +21,11 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
private NetworkInterface ninterface;
|
private NetworkInterface ninterface;
|
||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
private InetSocketAddress group;
|
private InetSocketAddress group;
|
||||||
private InetSocketAddress bind;
|
|
||||||
private int type;
|
private int type;
|
||||||
private List<MultipurposeSocketAddress> msas;
|
private List<MultiProtocolSocketAddress> msas;
|
||||||
private UUID selfUUID;
|
private UUID selfUUID;
|
||||||
|
|
||||||
private volatile Consumer<MultipurposeSocketAddress> con;
|
private volatile Consumer<MultiProtocolSocketAddress> con;
|
||||||
private long timeInterval;
|
private long timeInterval;
|
||||||
|
|
||||||
public long getTimeInterval() {
|
public long getTimeInterval() {
|
||||||
@@ -45,7 +36,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
this.timeInterval = timeInterval;
|
this.timeInterval = timeInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkInterface getNinterface() {
|
public NetworkInterface getInterface() {
|
||||||
return ninterface;
|
return ninterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,22 +45,24 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getBind() {
|
public InetSocketAddress getBind() {
|
||||||
return bind;
|
return (InetSocketAddress) soc.getLocalSocketAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<MultipurposeSocketAddress> getCon() {
|
public Consumer<MultiProtocolSocketAddress> getCon() {
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCon(Consumer<MultipurposeSocketAddress> con) {
|
public void setCon(Consumer<MultiProtocolSocketAddress> con) {
|
||||||
this.con = con;
|
this.con = con;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
|
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
|
||||||
List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException {
|
List<MultiProtocolSocketAddress> msas, UUID node, long timeInterval) throws IOException {
|
||||||
soc = new MulticastSocket(bind);
|
soc = new MulticastSocket(null);
|
||||||
|
soc.setReuseAddress(true);
|
||||||
|
soc.bind(bind);
|
||||||
|
soc.setNetworkInterface(ninterface);
|
||||||
soc.joinGroup(group, ninterface);
|
soc.joinGroup(group, ninterface);
|
||||||
this.bind = bind;
|
|
||||||
this.group = group;
|
this.group = group;
|
||||||
this.ninterface = ninterface;
|
this.ninterface = ninterface;
|
||||||
this.msas = msas;
|
this.msas = msas;
|
||||||
@@ -82,46 +75,22 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
|
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
|
||||||
try {
|
try {
|
||||||
while (!closed) {
|
while (!closed) {
|
||||||
HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>();
|
/*MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
||||||
synchronized (msas) {
|
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());*/
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) {
|
if(msas!=null) {
|
||||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator
|
synchronized (msas) {
|
||||||
.next();
|
for (Iterator<MultiProtocolSocketAddress> iterator = msas.iterator(); iterator.hasNext(); ) {
|
||||||
|
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator
|
||||||
|
.next();
|
||||||
|
|
||||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
try {
|
||||||
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());
|
send(multiProtocolSocketAddress);
|
||||||
try {
|
} catch (NoRouteToHostException | UnknownHostException e) {
|
||||||
if (s.add(mpsa)) {
|
|
||||||
boolean bf = true;
|
|
||||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
|
||||||
while (ei.hasMoreElements()) {
|
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
|
||||||
if (inetAddress.equals(multipurposeSocketAddress.getInetAddress())) {
|
|
||||||
bf = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (bf) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dops = new DataOutputStream(baos);
|
|
||||||
dops.writeInt(multipurposeSocketAddress.getPort());
|
|
||||||
dops.writeUTF(multipurposeSocketAddress.getType());
|
|
||||||
dops.writeLong(selfUUID.getMostSignificantBits());
|
|
||||||
dops.writeLong(selfUUID.getLeastSignificantBits());
|
|
||||||
dops.close();
|
|
||||||
byte[] b = baos.toByteArray();
|
|
||||||
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
|
||||||
soc.send(dp);
|
|
||||||
if(debug)
|
|
||||||
System.out.println("发送广播:"+multipurposeSocketAddress+" "+group+" "+selfUUID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NoRouteToHostException|UnknownHostException e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(timeInterval);
|
Thread.sleep(timeInterval);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -143,28 +112,9 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
try {
|
try {
|
||||||
loop:while (!closed) {
|
loop:while (!closed) {
|
||||||
try {
|
try {
|
||||||
byte[] b = new byte[65535];
|
MultiProtocolSocketAddress mpsa = receive();
|
||||||
DatagramPacket dp = new DatagramPacket(b, b.length);
|
if (mpsa == null) continue;
|
||||||
soc.receive(dp);
|
InetAddress mpsai=mpsa.getInetAddress();
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
|
||||||
DataInputStream dis = new DataInputStream(bis);
|
|
||||||
int port = dis.readInt();
|
|
||||||
String type = dis.readUTF();
|
|
||||||
long h=dis.readLong();
|
|
||||||
long l=dis.readLong();
|
|
||||||
UUID uid=new UUID(h, l);
|
|
||||||
dis.close();
|
|
||||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(type, dp.getAddress().getHostAddress(),
|
|
||||||
port);
|
|
||||||
if(debug) {
|
|
||||||
if(uid.equals(selfUUID)) {
|
|
||||||
System.out.println("丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
|
||||||
continue;
|
|
||||||
}else {
|
|
||||||
System.out.println("接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InetAddress mpsai=mpsa.getInetAddress();
|
|
||||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
||||||
while (ei.hasMoreElements()) {
|
while (ei.hasMoreElements()) {
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||||
@@ -186,7 +136,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
if(!isClosed())
|
if(!isClosed())
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}catch (IOException e) {
|
}catch (IOException e) {
|
||||||
System.out.println(bind + " " + group + " " + ninterface);
|
System.out.println(getBind() + " " + group + " " + ninterface);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
@@ -196,7 +146,48 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiProtocolSocketAddress receive() throws IOException {
|
||||||
|
byte[] b = new byte[65535];
|
||||||
|
DatagramPacket dp = new DatagramPacket(b, b.length);
|
||||||
|
soc.receive(dp);
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
||||||
|
DataInputStream dis = new DataInputStream(bis);
|
||||||
|
String val = dis.readUTF();
|
||||||
|
long h=dis.readLong();
|
||||||
|
long l=dis.readLong();
|
||||||
|
UUID uid=new UUID(h, l);
|
||||||
|
dis.close();
|
||||||
|
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(val);
|
||||||
|
if(mpsa.getInetAddress().isAnyLocalAddress()){
|
||||||
|
mpsa=new MultiProtocolSocketAddress(mpsa.getProtocol(),dp.getAddress().getHostAddress(),mpsa.getPort());
|
||||||
|
}
|
||||||
|
if(debug) {
|
||||||
|
if(uid.equals(selfUUID)) {
|
||||||
|
System.out.println(ninterface.getDisplayName()+" 丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||||
|
return null;
|
||||||
|
}else {
|
||||||
|
System.out.println(ninterface.getDisplayName()+ " 接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mpsa;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dops = new DataOutputStream(baos);
|
||||||
|
dops.writeUTF(multiProtocolSocketAddress.toString());
|
||||||
|
dops.writeLong(selfUUID.getMostSignificantBits());
|
||||||
|
dops.writeLong(selfUUID.getLeastSignificantBits());
|
||||||
|
dops.close();
|
||||||
|
byte[] b = baos.toByteArray();
|
||||||
|
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
||||||
|
soc.send(dp);
|
||||||
|
if(debug)
|
||||||
|
System.out.println(ninterface.getDisplayName()+" 发送广播:"+ multiProtocolSocketAddress +" "+group+" "+selfUUID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+101
-95
@@ -3,14 +3,7 @@ package org.kne.cloud.network;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.DatagramSocket;
|
import java.net.*;
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -18,7 +11,6 @@ import java.util.*;
|
|||||||
import javax.net.ServerSocketFactory;
|
import javax.net.ServerSocketFactory;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
@@ -27,17 +19,18 @@ import com.google.gson.JsonParseException;
|
|||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
|
import org.kne.cloud.network.ntp.NTPSocketType;
|
||||||
|
|
||||||
public class MultipurposeSocketAddress implements Serializable{
|
public class MultiProtocolSocketAddress implements Serializable{
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
|
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory()));
|
socketTypeRegister.put("tcp", TCPSocketType.getInstance());
|
||||||
socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory()));
|
socketTypeRegister.put("udp", UDPSocketType.getInstance());
|
||||||
|
socketTypeRegister.put("ntp", NTPSocketType.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -46,46 +39,59 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return socketTypeRegister;
|
return socketTypeRegister;
|
||||||
}
|
}
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private String type;
|
private String protocol;
|
||||||
private String host;
|
private String host;
|
||||||
private ProtocolStack ps=new ProtocolStack();
|
private ProtocolStack ps=new ProtocolStack();
|
||||||
|
|
||||||
|
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String hostport) {
|
public MultiProtocolSocketAddress(String hostport) {
|
||||||
this(hostport,"TCP");
|
this(hostport,"tcp");
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(String hostport,String defaulttype) {
|
public MultiProtocolSocketAddress(String hostport, String defaultprotocol) {
|
||||||
hostport=hostport.trim();
|
try {
|
||||||
if (hostport.startsWith("{")) {
|
if (!hostport.contains("://")) {
|
||||||
int i=hostport.indexOf("}");
|
hostport = defaultprotocol.toLowerCase() + "://" + hostport;
|
||||||
if(i==-1) {
|
|
||||||
throw new IllegalArgumentException("missing }");
|
|
||||||
}
|
}
|
||||||
type=hostport.substring(1, i);
|
URI uri = new URI(hostport);
|
||||||
hostport=hostport.substring(i+1);
|
|
||||||
}else {
|
this.protocol = uri.getScheme().toLowerCase();
|
||||||
type=defaulttype;
|
this.host = uri.getHost();
|
||||||
|
if (this.host == null) {
|
||||||
|
throw new IllegalArgumentException("无效的主机地址: " + hostport);
|
||||||
|
}
|
||||||
|
this.port = resolvePort(uri, this.protocol);
|
||||||
|
|
||||||
|
// 可选:处理查询参数
|
||||||
|
String query = uri.getQuery();
|
||||||
|
if (query != null && !query.isEmpty()) {
|
||||||
|
// 解析查询参数作为扩展信息
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IllegalArgumentException("无效的地址格式: " + hostport, e);
|
||||||
}
|
}
|
||||||
int index =hostport.lastIndexOf(":");
|
}
|
||||||
host=hostport.substring(0,index);
|
|
||||||
host=host.replace("[", "");
|
private int resolvePort(URI uri, String protocol) {
|
||||||
host=host.replace("]", "");
|
int port = uri.getPort();
|
||||||
String tps=hostport.substring(index+1);
|
if (port != -1) {
|
||||||
int v=tps.indexOf("(");
|
return port;
|
||||||
if(v!=-1) {
|
|
||||||
ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
|
|
||||||
tps=tps.substring(0, v);
|
|
||||||
}
|
}
|
||||||
port=Integer.parseInt(tps);
|
SocketType socketType = socketTypeRegister.get(protocol);
|
||||||
if(port<0) {
|
if (socketType != null) {
|
||||||
throw new IllegalArgumentException("port < 0");
|
int defaultPort = socketType.getDefaultPort();
|
||||||
|
if (defaultPort != -1) {
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"未指定端口号,且协议 " + protocol + " 没有默认端口: " + uri
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(host, port, ps, type);
|
return Objects.hash(host, port, ps, protocol);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
@@ -95,9 +101,9 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return false;
|
return false;
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||||
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
|
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
|
||||||
&& Objects.equals(type, other.type);
|
&& Objects.equals(protocol, other.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals2(Object obj) {
|
public boolean equals2(Object obj) {
|
||||||
@@ -107,7 +113,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return false;
|
return false;
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||||
InetAddress addra=null;
|
InetAddress addra=null;
|
||||||
try {
|
try {
|
||||||
addra = InetAddress.getByName(host);
|
addra = InetAddress.getByName(host);
|
||||||
@@ -121,25 +127,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
boolean hostequals=Objects.equals(addra, addrb);
|
boolean hostequals=Objects.equals(addra, addrb);
|
||||||
|
|
||||||
return hostequals && port == other.port && Objects.equals(ps, other.ps)
|
return hostequals && port == other.port && Objects.equals(ps, other.ps)
|
||||||
&& Objects.equals(type, other.type);
|
&& Objects.equals(protocol, other.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String host2, int port2) {
|
public MultiProtocolSocketAddress(String host2, int port2) {
|
||||||
this("TCP", host2, port2);
|
this("tcp", host2, port2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String type,String host2, int port2) {
|
public MultiProtocolSocketAddress(String protocol, String host2, int port2) {
|
||||||
host=host2;
|
host=host2;
|
||||||
port=port2;
|
port=port2;
|
||||||
this.type=type;
|
this.protocol = protocol;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) {
|
public MultiProtocolSocketAddress(InetSocketAddress remoteSocketAddress) {
|
||||||
this("TCP",remoteSocketAddress);
|
this("tcp",remoteSocketAddress);
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) {
|
public MultiProtocolSocketAddress(String protocol, InetSocketAddress remoteSocketAddress) {
|
||||||
host=remoteSocketAddress.getAddress().getHostAddress();
|
host=remoteSocketAddress.getAddress().getHostAddress();
|
||||||
port=remoteSocketAddress.getPort();
|
port=remoteSocketAddress.getPort();
|
||||||
this.type=type;
|
this.protocol = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -149,8 +155,8 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
public String getType() {
|
public String getProtocol() {
|
||||||
return type;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getSocketAddress() {
|
public InetSocketAddress getSocketAddress() {
|
||||||
@@ -159,27 +165,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb=new StringBuilder();
|
try {
|
||||||
if(!"TCP".equals(type)) {
|
// 获取协议的默认端口
|
||||||
sb.append('{').append(type).append('}');
|
SocketType socketType = socketTypeRegister.get(protocol);
|
||||||
|
int defaultPort = (socketType != null) ? socketType.getDefaultPort() : -1;
|
||||||
|
|
||||||
|
// 如果当前端口等于默认端口,则隐去端口号
|
||||||
|
int portToUse = (defaultPort != -1 && this.port == defaultPort) ? -1 : this.port;
|
||||||
|
|
||||||
|
URI uri = new URI(protocol, null, host, portToUse, null, null, null);
|
||||||
|
return uri.toString();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return protocol + "://" + host + ":" + port;
|
||||||
}
|
}
|
||||||
if(host.contains(":")) {
|
|
||||||
sb.append('[').append( host).append( "]:").append( port);
|
|
||||||
}else {
|
|
||||||
sb.append( host).append( ':').append( port);
|
|
||||||
}
|
|
||||||
if(!ps.isEmpty()) {
|
|
||||||
sb.append('(').append(ps.pop().getName()).append(')');
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -194,7 +198,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -209,7 +213,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket() throws UnknownHostException, IOException {
|
public Socket connectSocket() throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -223,7 +227,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
|
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -244,7 +248,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -265,7 +269,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -280,7 +284,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -294,7 +298,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -319,7 +323,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
|
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
|
||||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -327,7 +331,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
|
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -337,7 +341,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
|
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
|
||||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -345,7 +349,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
|
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
|
||||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -355,13 +359,13 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public boolean isStream() {
|
public boolean isStream() {
|
||||||
return checkIsStream(type);
|
return checkIsStream(protocol);
|
||||||
}
|
}
|
||||||
public static boolean checkIsStream(String type2) {
|
public static boolean checkIsStream(String type2) {
|
||||||
return socketTypeRegister.get(type2).isStream();
|
return socketTypeRegister.get(type2).isStream();
|
||||||
}
|
}
|
||||||
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
|
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -376,7 +380,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return dgd;
|
return dgd;
|
||||||
}
|
}
|
||||||
public DatagramSocket connectDatagramSocket() throws IOException {
|
public DatagramSocket connectDatagramSocket() throws IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -390,7 +394,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return dgd;
|
return dgd;
|
||||||
}
|
}
|
||||||
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
|
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -405,7 +409,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
|
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
|
||||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -413,7 +417,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
|
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -427,7 +431,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return InetAddress.getAllByName(host);
|
return InetAddress.getAllByName(host);
|
||||||
}
|
}
|
||||||
public boolean supportNIO() {
|
public boolean supportNIO() {
|
||||||
SocketType stp= socketTypeRegister.get(getType());
|
SocketType stp= socketTypeRegister.get(getProtocol());
|
||||||
if(stp==null)
|
if(stp==null)
|
||||||
return false;
|
return false;
|
||||||
if(stp.getSocketChannelFactory()==null) {
|
if(stp.getSocketChannelFactory()==null) {
|
||||||
@@ -437,35 +441,37 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){
|
public static JsonDeserializer<MultiProtocolSocketAddress>getDefaultJsonDeserializer(){
|
||||||
return new JsonDeserializer<MultipurposeSocketAddress>() {
|
return new JsonDeserializer<MultiProtocolSocketAddress>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
public MultiProtocolSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
||||||
throws JsonParseException {
|
throws JsonParseException {
|
||||||
if(arg0.isJsonPrimitive()) {
|
if(arg0.isJsonPrimitive()) {
|
||||||
JsonPrimitive jp=(JsonPrimitive) arg0;
|
JsonPrimitive jp=(JsonPrimitive) arg0;
|
||||||
if(jp.isString()) {
|
if(jp.isString()) {
|
||||||
return new MultipurposeSocketAddress(jp.getAsString());
|
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(jp.getAsString());
|
||||||
|
// System.out.println(msa);
|
||||||
|
return msa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new JsonParseException("not a string:"+arg0);
|
throw new JsonParseException("not a string:"+arg0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){
|
public static JsonSerializer<MultiProtocolSocketAddress>getDefaultJsonSerializer(){
|
||||||
return new JsonSerializer<MultipurposeSocketAddress>() {
|
return new JsonSerializer<MultiProtocolSocketAddress>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
public JsonElement serialize(MultiProtocolSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
||||||
return new JsonPrimitive(arg0.toString());
|
return new JsonPrimitive(arg0.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
|
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
|
||||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer());
|
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonDeserializer());
|
||||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer());
|
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonSerializer());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
public interface NetworkService {
|
public interface NetworkService {
|
||||||
public void listen(MultipurposeSocketAddress msa);
|
public void listen(MultiProtocolSocketAddress msa);
|
||||||
public void unlisten(MultipurposeSocketAddress msc);
|
public void unlisten(MultiProtocolSocketAddress msc);
|
||||||
|
|
||||||
public void connect(MultipurposeSocketAddress msa);
|
public void connect(MultiProtocolSocketAddress msa);
|
||||||
public void unconnect(MultipurposeSocketAddress msc);
|
public void unconnect(MultiProtocolSocketAddress msc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.*;
|
|
||||||
import java.nio.channels.spi.AbstractInterruptibleChannel;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class PortRelay {
|
public class PortRelay {
|
||||||
private int port;
|
private int port;
|
||||||
@@ -16,8 +10,8 @@ public class PortRelay {
|
|||||||
public PortRelay(int port, HostPortMap services) throws IOException {
|
public PortRelay(int port, HostPortMap services) throws IOException {
|
||||||
this.services = services;
|
this.services = services;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
MultiProtocolSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
||||||
ssc=new SocketListener(new MultipurposeSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
ssc=new SocketListener(new MultiProtocolSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
||||||
}
|
}
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
ssc.setCon((s)->{
|
ssc.setCon((s)->{
|
||||||
@@ -30,7 +24,7 @@ public class PortRelay {
|
|||||||
}else {
|
}else {
|
||||||
nx=pds.getProtocolStack().pop().getName();
|
nx=pds.getProtocolStack().pop().getName();
|
||||||
}
|
}
|
||||||
MultipurposeSocketAddress hp=services.get(nx);
|
MultiProtocolSocketAddress hp=services.get(nx);
|
||||||
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
|
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
|
||||||
System.out.println(sa.getAddress().getHostAddress() + ":" + port
|
System.out.println(sa.getAddress().getHostAddress() + ":" + port
|
||||||
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
|
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ServiceElement {
|
public class ServiceElement {
|
||||||
public String name;
|
public String name;
|
||||||
public String protocol;
|
public String protocol;
|
||||||
public MultipurposeSocketAddress ipport;
|
public MultiProtocolSocketAddress ipport;
|
||||||
public ServiceElement(String ini) throws UnknownHostException {
|
public ServiceElement(String ini) throws UnknownHostException {
|
||||||
String[]t=ini.split("\\$");
|
String[]t=ini.split("\\$");
|
||||||
if(t.length==1) {
|
if(t.length==1) {
|
||||||
protocol="";
|
protocol="";
|
||||||
ipport=new MultipurposeSocketAddress(t[0]);
|
ipport=new MultiProtocolSocketAddress(t[0]);
|
||||||
}else {
|
}else {
|
||||||
protocol=t[0];
|
protocol=t[0];
|
||||||
ipport=new MultipurposeSocketAddress(t[1]);
|
ipport=new MultiProtocolSocketAddress(t[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public ServiceElement() {
|
public ServiceElement() {
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class ServiceToSocketProxy extends Proxy{
|
public class ServiceToSocketProxy extends Proxy{
|
||||||
public NetworkService getSrc() {
|
public NetworkService getSrc() {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress getDes() {
|
public MultiProtocolSocketAddress getDes() {
|
||||||
return des;
|
return des;
|
||||||
}
|
}
|
||||||
public ServiceToSocketProxy(String l, String r) {
|
public ServiceToSocketProxy(String l, String r) {
|
||||||
src=getRegister().get(l.substring(1, l.length()-1));
|
src=getRegister().get(l.substring(1, l.length()-1));
|
||||||
des=new MultipurposeSocketAddress(r);
|
des=new MultiProtocolSocketAddress(r);
|
||||||
}
|
}
|
||||||
private NetworkService src;
|
private NetworkService src;
|
||||||
private MultipurposeSocketAddress des;
|
private MultiProtocolSocketAddress des;
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// TODO 自动生成的方法存根
|
// TODO 自动生成的方法存根
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public SocketChannelListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public SocketChannelListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
|
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
|
||||||
@@ -62,9 +62,9 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
protected void open() throws UnknownHostException, IOException {
|
protected void open() throws UnknownHostException, IOException {
|
||||||
if(serverSocketChannel==null)
|
if(serverSocketChannel==null)
|
||||||
serverSocketChannel=multipurposeSocketAddress.listenServerSocketChannel();
|
serverSocketChannel= multiProtocolSocketAddress.listenServerSocketChannel();
|
||||||
if(serverSocketChannel==null) {
|
if(serverSocketChannel==null) {
|
||||||
throw new IOException(multipurposeSocketAddress.getType()+" unsupport SocketChannel");
|
throw new IOException(multiProtocolSocketAddress.getProtocol()+" unsupport SocketChannel");
|
||||||
}
|
}
|
||||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||||
}
|
}
|
||||||
@@ -82,8 +82,8 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<SocketChannel> getCon() {
|
public Consumer<SocketChannel> getCon() {
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ package org.kne.cloud.network;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
|
|
||||||
public class SocketListener implements Closeable,AutoCloseable{
|
public class SocketListener implements Closeable,AutoCloseable{
|
||||||
protected ServerSocket serverSocket;
|
protected ServerSocket serverSocket;
|
||||||
public ServerSocket getServerSocket() {
|
public ServerSocket getServerSocket() {
|
||||||
@@ -53,10 +50,10 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public SocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public SocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public SocketListener(ServerSocket tserverSocket) throws IOException {
|
public SocketListener(ServerSocket tserverSocket) throws IOException {
|
||||||
@@ -65,7 +62,7 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
protected void open() throws UnknownHostException, IOException {
|
protected void open() throws UnknownHostException, IOException {
|
||||||
if(serverSocket==null)
|
if(serverSocket==null)
|
||||||
serverSocket=multipurposeSocketAddress.listenServerSocket();
|
serverSocket= multiProtocolSocketAddress.listenServerSocket();
|
||||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||||
}
|
}
|
||||||
public void close() {
|
public void close() {
|
||||||
@@ -82,8 +79,8 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<Socket> getCon() {
|
public Consumer<Socket> getCon() {
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class SocketToServiceProxy extends Proxy {
|
public class SocketToServiceProxy extends Proxy {
|
||||||
public SocketToServiceProxy(String l, String r) {
|
public SocketToServiceProxy(String l, String r) {
|
||||||
src=new MultipurposeSocketAddress(l);
|
src=new MultiProtocolSocketAddress(l);
|
||||||
des=getRegister().get(r.substring(1, r.length()-1)) ;
|
des=getRegister().get(r.substring(1, r.length()-1)) ;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress getSrc() {
|
public MultiProtocolSocketAddress getSrc() {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
public NetworkService getDes() {
|
public NetworkService getDes() {
|
||||||
return des;
|
return des;
|
||||||
}
|
}
|
||||||
private MultipurposeSocketAddress src;
|
private MultiProtocolSocketAddress src;
|
||||||
private NetworkService des;
|
private NetworkService des;
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketOption;
|
|
||||||
import java.net.SocketOptions;
|
|
||||||
import java.net.StandardSocketOptions;
|
import java.net.StandardSocketOptions;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -15,8 +12,8 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
private SocketListener sl;
|
private SocketListener sl;
|
||||||
private SocketChannelListener scl;
|
private SocketChannelListener scl;
|
||||||
private DatagramSocketListener dsl;
|
private DatagramSocketListener dsl;
|
||||||
private MultipurposeSocketAddress listen, cbind, defaultConnect;
|
private MultiProtocolSocketAddress listen, cbind, defaultConnect;
|
||||||
private Map<String, MultipurposeSocketAddress> detectedConnect;
|
private Map<String, MultiProtocolSocketAddress> detectedConnect;
|
||||||
private SocketBridgeFactory defaultFactory;
|
private SocketBridgeFactory defaultFactory;
|
||||||
private SocketChannelBridgeFactory defaultChannelFactory;
|
private SocketChannelBridgeFactory defaultChannelFactory;
|
||||||
private Map<String, SocketBridgeFactory> detectedFactory;
|
private Map<String, SocketBridgeFactory> detectedFactory;
|
||||||
@@ -30,28 +27,28 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
this.defaultSoTimeout = defaultSoTimeout;
|
this.defaultSoTimeout = defaultSoTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) throws IOException {
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress connect) throws IOException {
|
||||||
this(listen, new MultipurposeSocketAddress(new InetSocketAddress(0)), connect);
|
this(listen, new MultiProtocolSocketAddress(new InetSocketAddress(0)), connect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(String l, String r) throws IOException {
|
public SocketToSocketProxy(String l, String r) throws IOException {
|
||||||
this(new MultipurposeSocketAddress(l), new MultipurposeSocketAddress(r));
|
this(new MultiProtocolSocketAddress(l), new MultiProtocolSocketAddress(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
|
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
|
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory,SocketChannelBridgeFactory defaultChannelFactory)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory, SocketChannelBridgeFactory defaultChannelFactory)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.listen = listen;
|
this.listen = listen;
|
||||||
this.cbind = cbind;
|
this.cbind = cbind;
|
||||||
@@ -66,8 +63,8 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect) throws IOException {
|
MultiProtocolSocketAddress defaultConnect) throws IOException {
|
||||||
this(listen, cbind, defaultConnect,null);
|
this(listen, cbind, defaultConnect,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +109,7 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
|
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
|
||||||
if (!ps.isEmpty()) {
|
if (!ps.isEmpty()) {
|
||||||
String pname=ps.pop().getName();
|
String pname=ps.pop().getName();
|
||||||
MultipurposeSocketAddress pmsa = detectedConnect.get(pname);
|
MultiProtocolSocketAddress pmsa = detectedConnect.get(pname);
|
||||||
if(pmsa!=null) {
|
if(pmsa!=null) {
|
||||||
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
|
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
|
||||||
}else {
|
}else {
|
||||||
@@ -178,19 +175,19 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
sb.run();
|
sb.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getListen() {
|
public MultiProtocolSocketAddress getListen() {
|
||||||
return listen;
|
return listen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getCbind() {
|
public MultiProtocolSocketAddress getCbind() {
|
||||||
return cbind;
|
return cbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getDefaultConnect() {
|
public MultiProtocolSocketAddress getDefaultConnect() {
|
||||||
return defaultConnect;
|
return defaultConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, MultipurposeSocketAddress> getDetectedConnect() {
|
public Map<String, MultiProtocolSocketAddress> getDetectedConnect() {
|
||||||
return detectedConnect;
|
return detectedConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class SocketType {
|
|||||||
private DatagramSocketFactory datagramSocketFactory;
|
private DatagramSocketFactory datagramSocketFactory;
|
||||||
private DatagramServerSocketFactory datagramServerSocketFactory;
|
private DatagramServerSocketFactory datagramServerSocketFactory;
|
||||||
private boolean stream;
|
private boolean stream;
|
||||||
|
private int defaultPort=-1;
|
||||||
|
|
||||||
public SocketFactory Channel() {
|
public SocketFactory Channel() {
|
||||||
return socketFactory;
|
return socketFactory;
|
||||||
@@ -74,5 +75,7 @@ public class SocketType {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDefaultPort(){
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
|
public class TCPSocketType extends SocketType {
|
||||||
|
private static final TCPSocketType INSTANCE = new TCPSocketType();
|
||||||
|
|
||||||
|
private TCPSocketType() {
|
||||||
|
super(
|
||||||
|
new DefaultSocketFactory(),
|
||||||
|
new DefaultServerSocketFactory(),
|
||||||
|
new DefaultSocketChannelFactory(),
|
||||||
|
new DefaultServerSocketChannelFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TCPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStream() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
|
public class UDPSocketType extends SocketType {
|
||||||
|
private static final UDPSocketType INSTANCE = new UDPSocketType();
|
||||||
|
|
||||||
|
protected UDPSocketType() {
|
||||||
|
super(
|
||||||
|
new DefaultDatagramSocketFactory(),
|
||||||
|
new DefaultDatagramServerSocketFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UDPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStream() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,13 @@ import java.io.IOException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketImpl;
|
import java.net.SocketImpl;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.ini4j.Profile.Section;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
public class FrpcServerSocket extends ServerSocket {
|
public class FrpcServerSocket extends ServerSocket {
|
||||||
private FrpcProcess frpc;
|
private FrpcProcess frpc;
|
||||||
@@ -76,10 +73,10 @@ public class FrpcServerSocket extends ServerSocket {
|
|||||||
return frpcini.getRemotePort(tunnel);
|
return frpcini.getRemotePort(tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTunnelSocketAddress() {
|
public MultiProtocolSocketAddress getTunnelSocketAddress() {
|
||||||
if (!isBound())
|
if (!isBound())
|
||||||
return null;
|
return null;
|
||||||
return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -116,9 +116,7 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
|||||||
}*/
|
}*/
|
||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
@@ -128,15 +126,14 @@ import java.net.SocketException;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket;
|
import org.kne.cloud.network.DatagramServerSocket;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
|
||||||
|
|
||||||
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatagramSocket ds;
|
private DatagramSocket ds;
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import javax.annotation.processing.Filer;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import javax.lang.model.element.Element;
|
|
||||||
import javax.tools.FileObject;
|
|
||||||
import javax.tools.JavaFileObject;
|
|
||||||
import javax.tools.JavaFileManager.Location;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonIOException;
|
import com.google.gson.JsonIOException;
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
public class KLALBConfig extends ArrayList<KLALBConfigItem>{
|
public class KLALBConfig extends ArrayList<KLALBConfigItem>{
|
||||||
|
|
||||||
|
public KLALBConfig(){
|
||||||
|
}
|
||||||
|
public static KLALBConfig getDefault(){
|
||||||
|
KLALBConfig config=new KLALBConfig();
|
||||||
|
config.add(new KLALBControllerConfigItem());
|
||||||
|
return config;
|
||||||
|
}
|
||||||
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
|
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
|
||||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||||
Gson gson=gb.create();
|
Gson gson=gb.create();
|
||||||
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
|
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.IPMulticastDiscovery;
|
import org.kne.cloud.network.IPMulticastDiscovery;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.PortPair;
|
import org.kne.cloud.network.PortPair;
|
||||||
import org.kne.cloud.network.SocketType;
|
import org.kne.cloud.network.SocketType;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
@@ -67,7 +67,7 @@ public class KLALBController {
|
|||||||
public HighAccuracyClock getClock() {
|
public HighAccuracyClock getClock() {
|
||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
private final long TIME_WINDOW=5000000000L;
|
private final long TIME_WINDOW=2000000000L;
|
||||||
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
|
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||||
@@ -76,8 +76,13 @@ public class KLALBController {
|
|||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> selflineTable = new ArrayList<>();
|
private List<MultiProtocolSocketAddress> selflineTable = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getListenSocketAddress() {
|
||||||
|
return listensSocketAddress;
|
||||||
|
}
|
||||||
private static final boolean debug=false;
|
private static final boolean debug=false;
|
||||||
private static final boolean showpacket = false;
|
private static final boolean showpacket = false;
|
||||||
|
|
||||||
@@ -86,10 +91,12 @@ public class KLALBController {
|
|||||||
|
|
||||||
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
|
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
|
||||||
|
|
||||||
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
private NetworkInterfaceManager networkInterfaceManager=new NetworkInterfaceManager();
|
||||||
|
public NetworkInterfaceManager getNetworkInterfaceManager() {
|
||||||
|
return networkInterfaceManager;
|
||||||
|
}
|
||||||
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||||
return networkInterfaceExcept;
|
return networkInterfaceManager.getNetworkInterfaceExcept();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<InetAddress> dnsAddresses = new ArrayList<>();
|
private List<InetAddress> dnsAddresses = new ArrayList<>();
|
||||||
@@ -111,50 +118,23 @@ public class KLALBController {
|
|||||||
|
|
||||||
private Timer twk = new Timer("网卡检测扫描计时器", true);
|
private Timer twk = new Timer("网卡检测扫描计时器", true);
|
||||||
|
|
||||||
private List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
|
||||||
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
|
||||||
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
|
||||||
while (eu.hasMoreElements()) {
|
|
||||||
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
|
||||||
|
|
||||||
if (networkInterface.getDisplayName()
|
|
||||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (networkInterface.isUp()) {
|
|
||||||
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
|
||||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
|
||||||
while (ei.hasMoreElements()) {
|
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
|
||||||
if (checkIsSelfLocator(inetAddress)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
addresses.add(inetAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimerTask tsk1 = new TimerTask() {
|
private TimerTask tsk1 = new TimerTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
List<InetAddress> localaddress = getAllNetworkInterfaceAddress();
|
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
|
||||||
|
|
||||||
for (InetAddress inetAddress : localaddress) {
|
for (InetAddress inetAddress : localaddress) {
|
||||||
if (!inetAddress.isLoopbackAddress())
|
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = listens.iterator(); iterator.hasNext();) {
|
MultiProtocolSocketAddress tcpl = (MultiProtocolSocketAddress) iterator.next();
|
||||||
MultipurposeSocketAddress tcpl = (MultipurposeSocketAddress) iterator.next();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tcpl.getInetAddress().isAnyLocalAddress()
|
if (tcpl.getInetAddress().isAnyLocalAddress()
|
||||||
|| tcpl.getInetAddress().equals(inetAddress)) {
|
|| tcpl.getInetAddress().equals(inetAddress)) {
|
||||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(tcpl.getType(),
|
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
|
||||||
inetAddress.getHostAddress(), tcpl.getPort());
|
inetAddress.getHostAddress(), tcpl.getPort());
|
||||||
// System.out.println(bind);
|
// System.out.println(bind);
|
||||||
synchronized (selflineTable) {
|
synchronized (selflineTable) {
|
||||||
@@ -175,46 +155,45 @@ public class KLALBController {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
Set<MultipurposeSocketAddress> st = new HashSet<>();
|
Set<MultiProtocolSocketAddress> st = new HashSet<>();
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
if (link instanceof KLALBRemoteLink) {
|
||||||
if (link instanceof KLALBRemoteLink) {
|
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
|
||||||
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
|
if (multipurposeSocketAddress.getSocketAddress() != null)
|
||||||
if (multipurposeSocketAddress.getSocketAddress() != null)
|
st.add(multipurposeSocketAddress.getSocketAddress());
|
||||||
st.add(multipurposeSocketAddress.getSocketAddress());
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = st.iterator(); iterator.hasNext();) {
|
for (MultiProtocolSocketAddress target : st) {
|
||||||
MultipurposeSocketAddress target = (MultipurposeSocketAddress) iterator.next();
|
addRemoteLines(target);
|
||||||
addRemoteLines(target);
|
}
|
||||||
}
|
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
|
||||||
IPv6NetworkLink link = iterator.next();
|
|
||||||
if (link instanceof KLALBRemoteLink) {
|
|
||||||
KLALBRemoteLink reml = (KLALBRemoteLink) link;
|
|
||||||
if (reml.getSocketAddress() != null) {
|
|
||||||
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
|
|
||||||
try {
|
|
||||||
MultipurposeSocketAddress iadr=reml.getBindAddress();
|
|
||||||
if(iadr!=null) {
|
|
||||||
if (!localaddress.contains(iadr.getInetAddress())) {
|
|
||||||
reml.close();
|
|
||||||
srv6Router.getLinkTabel().remove(reml);
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
|
|
||||||
reml.close();
|
/*
|
||||||
}
|
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||||
} catch (UnknownHostException e) {
|
if (link instanceof KLALBRemoteLink) {
|
||||||
reml.close();
|
KLALBRemoteLink reml = (KLALBRemoteLink) link;
|
||||||
srv6Router.getLinkTabel().remove(reml);
|
if (reml.getSocketAddress() != null) {
|
||||||
e.printStackTrace();
|
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
|
||||||
}
|
try {
|
||||||
}
|
MultipurposeSocketAddress iadr = reml.getBindAddress();
|
||||||
}
|
if (iadr != null) {
|
||||||
}
|
if (!localaddress.contains(iadr.getInetAddress())) {
|
||||||
|
reml.close();
|
||||||
|
srv6Router.getLinkTabel().remove(reml);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
reml.close();
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
reml.close();
|
||||||
|
srv6Router.getLinkTabel().remove(reml);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
} finally {
|
} finally {
|
||||||
lineslock.writeLock().unlock();
|
lineslock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -228,9 +207,9 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateBroadcastNetworkInterface() throws SocketException {
|
private void updateBroadcastNetworkInterface() throws SocketException {
|
||||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext(); ) {
|
||||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
||||||
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getNinterface().isUp())
|
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getInterface().isUp())
|
||||||
|| (configItem != null && configItem.isDenyLineTableBroadcast())) {
|
|| (configItem != null && configItem.isDenyLineTableBroadcast())) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
try {
|
try {
|
||||||
@@ -238,99 +217,86 @@ public class KLALBController {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if(debug)
|
if (debug)
|
||||||
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getNinterface());
|
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getInterface());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configItem != null && configItem.isDenyLineTableBroadcast()) {
|
if (configItem != null && configItem.isDenyLineTableBroadcast()) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Enumeration<NetworkInterface> eu2 = NetworkInterface.getNetworkInterfaces();
|
List<NetworkInterface> interfaceList = networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||||
while (eu2.hasMoreElements()) {
|
for(NetworkInterface networkInterface:interfaceList){
|
||||||
NetworkInterface networkInterface = (NetworkInterface) eu2.nextElement();
|
List<InetAddress> addressList= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||||
if (networkInterface.getDisplayName()
|
loop:for (InetAddress bidr:addressList) {
|
||||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkInterface.isUp()) {
|
|
||||||
|
|
||||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
|
||||||
loop: while (ei.hasMoreElements()) {
|
|
||||||
InetAddress bidr = (InetAddress) ei.nextElement();
|
|
||||||
if (checkIsSelfLocator(bidr)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
|
||||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
|
||||||
if (networkInterface.equals(ipMulticastDiscovery.getNinterface())
|
|
||||||
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
|
|
||||||
continue loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// InetAddress bidr=InetAddress.getByName("::0");
|
|
||||||
|
|
||||||
if (bidr instanceof Inet6Address) {
|
|
||||||
IPMulticastDiscovery ipd = new IPMulticastDiscovery(
|
|
||||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
|
||||||
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
|
|
||||||
DISCOVERY_PORT),
|
|
||||||
networkInterface, selflineTable, nodeuuid,10000L);
|
|
||||||
ipd.setCon((mpa) -> {
|
|
||||||
// System.out.println("添加本地IPv6链路:"+mpa);
|
|
||||||
try {
|
|
||||||
if (!checkIsSelf(mpa))
|
|
||||||
addRemoteLines(mpa);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ipd.start();
|
|
||||||
ipmd.add(ipd);
|
|
||||||
} else if (bidr instanceof Inet4Address) {
|
|
||||||
// bidr=InetAddress.getByName("0.0.0.0");
|
|
||||||
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(
|
|
||||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
|
||||||
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
|
|
||||||
DISCOVERY_PORT),
|
|
||||||
networkInterface, selflineTable,nodeuuid, 10000L);
|
|
||||||
ipd2.setCon((mpa) -> {
|
|
||||||
// System.out.println("添加本地IPv4链路:"+mpa);
|
|
||||||
try {
|
|
||||||
if (!checkIsSelf(mpa))
|
|
||||||
addRemoteLines(mpa);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ipd2.start();
|
|
||||||
ipmd.add(ipd2);
|
|
||||||
}
|
|
||||||
if(debug)
|
|
||||||
System.out.println("开启网卡地址广播:" + networkInterface);
|
|
||||||
} catch (BindException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (IPMulticastDiscovery ipMulticastDiscovery : ipmd) {
|
||||||
|
if (networkInterface.equals(ipMulticastDiscovery.getInterface())
|
||||||
|
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
|
||||||
|
continue loop;
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
try {
|
||||||
|
// InetAddress bidr=InetAddress.getByName("::0");
|
||||||
|
|
||||||
|
if (bidr instanceof Inet6Address) {
|
||||||
|
IPMulticastDiscovery ipd = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||||
|
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
|
||||||
|
DISCOVERY_PORT),
|
||||||
|
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||||
|
ipd.setCon((mpa) -> {
|
||||||
|
if (debug)
|
||||||
|
System.out.println("添加本地IPv6链路:" + mpa);
|
||||||
|
try {
|
||||||
|
if (!checkIsSelf(mpa))
|
||||||
|
addRemoteLines(mpa);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipd.start();
|
||||||
|
ipmd.add(ipd);
|
||||||
|
} else if (bidr instanceof Inet4Address) {
|
||||||
|
// bidr=InetAddress.getByName("0.0.0.0");
|
||||||
|
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||||
|
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
|
||||||
|
DISCOVERY_PORT),
|
||||||
|
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||||
|
ipd2.setCon((mpa) -> {
|
||||||
|
if (debug)
|
||||||
|
System.out.println("添加本地IPv4链路:" + mpa);
|
||||||
|
try {
|
||||||
|
if (!checkIsSelf(mpa))
|
||||||
|
addRemoteLines(mpa);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipd2.start();
|
||||||
|
ipmd.add(ipd2);
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
System.out.println("开启网卡地址广播:" + networkInterface);
|
||||||
|
} catch (BindException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean checkIsSelf(MultipurposeSocketAddress inetAddress) throws UnknownHostException {
|
private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
|
||||||
|
|
||||||
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|
||||||
|| selflineTable.contains(inetAddress);
|
|| selflineTable.contains(inetAddress);
|
||||||
@@ -345,16 +311,16 @@ public class KLALBController {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> nt = ntptable;
|
List<MultiProtocolSocketAddress> nt = ntptable;
|
||||||
// System.out.println("srv6 ip"+nb);
|
// System.out.println("srv6 ip"+nb);
|
||||||
if (nvc2 != null) {
|
if (nvc2 != null) {
|
||||||
Set<NTPPeer> sp = nvc2.getPeers();
|
Set<NTPPeer> sp = nvc2.getPeers();
|
||||||
for (MultipurposeSocketAddress server : nt) {
|
for (MultiProtocolSocketAddress server : nt) {
|
||||||
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
|
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
|
||||||
|
|
||||||
}
|
}
|
||||||
sp.removeIf((p) -> {
|
sp.removeIf((p) -> {
|
||||||
for (MultipurposeSocketAddress server : nt) {
|
for (MultiProtocolSocketAddress server : nt) {
|
||||||
if (server.equals(p.getAddress())) {
|
if (server.equals(p.getAddress())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -365,33 +331,30 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private TimerTask tsk3 = new TimerTask() {
|
public void tryConnectMore(){
|
||||||
|
if (srv6Router != null && routingProtocol != null) {
|
||||||
@Override
|
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
|
||||||
public void run() {
|
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
|
||||||
if (srv6Router != null && routingProtocol != null) {
|
IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next();
|
||||||
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
|
try {
|
||||||
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
|
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
|
||||||
IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next();
|
KLALBRoutingProtocol.DEFAULT_PORT);
|
||||||
try {
|
apiClient.requestOpenLines(iaddr, (v) -> {
|
||||||
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
|
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
|
||||||
KLALBRoutingProtocol.DEFAULT_PORT);
|
for (MultiProtocolSocketAddress msa : v) {
|
||||||
apiClient.requestOpenLines(iaddr, (v) -> {
|
// System.out.print(msa);
|
||||||
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
|
addRemoteLines(msa);
|
||||||
for (MultipurposeSocketAddress msa : v) {
|
}
|
||||||
// System.out.print(msa);
|
}).start();
|
||||||
addRemoteLines(msa);
|
});
|
||||||
}
|
} catch (IOException e) {
|
||||||
}).start();
|
e.printStackTrace();
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
|
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
|
||||||
return linkMonitor;
|
return linkMonitor;
|
||||||
@@ -403,6 +366,8 @@ public class KLALBController {
|
|||||||
|
|
||||||
private SocketType streamSocketType = new KLALBStreamSocketType();
|
private SocketType streamSocketType = new KLALBStreamSocketType();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class KLALBStreamSocketType extends SocketType {
|
public class KLALBStreamSocketType extends SocketType {
|
||||||
|
|
||||||
public KLALBStreamSocketType() {
|
public KLALBStreamSocketType() {
|
||||||
@@ -436,7 +401,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getSelflineTable() {
|
public List<MultiProtocolSocketAddress> getSelflineTable() {
|
||||||
return selflineTable;
|
return selflineTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,13 +437,13 @@ public class KLALBController {
|
|||||||
|
|
||||||
private KLALBControllerConfigItem configItem;
|
private KLALBControllerConfigItem configItem;
|
||||||
|
|
||||||
public void addRemoteLines(List<MultipurposeSocketAddress> select) {
|
public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
|
||||||
for (MultipurposeSocketAddress target : select) {
|
for (MultiProtocolSocketAddress target : select) {
|
||||||
addRemoteLines(target);
|
addRemoteLines(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KLALBRemoteLink> addRemoteLines(MultipurposeSocketAddress target) {
|
public List<KLALBRemoteLink> addRemoteLines(MultiProtocolSocketAddress target) {
|
||||||
lineslock.writeLock().lock();
|
lineslock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
List<KLALBRemoteLink> added = new ArrayList<>();
|
List<KLALBRemoteLink> added = new ArrayList<>();
|
||||||
@@ -492,7 +457,7 @@ public class KLALBController {
|
|||||||
while (ei.hasMoreElements()) {
|
while (ei.hasMoreElements()) {
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||||
try {
|
try {
|
||||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(
|
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(
|
||||||
inetAddress.getHostAddress(), 0);
|
inetAddress.getHostAddress(), 0);
|
||||||
try {
|
try {
|
||||||
if (target.getInetAddress().isLoopbackAddress()
|
if (target.getInetAddress().isLoopbackAddress()
|
||||||
@@ -553,7 +518,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KLALBRemoteLink> removeRemoteLines(MultipurposeSocketAddress mpsa) {
|
public List<KLALBRemoteLink> removeRemoteLines(MultiProtocolSocketAddress mpsa) {
|
||||||
|
|
||||||
List<KLALBRemoteLink> rmved = new ArrayList<>();
|
List<KLALBRemoteLink> rmved = new ArrayList<>();
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
@@ -569,7 +534,7 @@ public class KLALBController {
|
|||||||
return rmved;
|
return rmved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException {
|
public Inet6Address getRemoteVaddrBySocketAddress(MultiProtocolSocketAddress target) throws SocketTimeoutException {
|
||||||
KLALBRemoteLink kr = null;
|
KLALBRemoteLink kr = null;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -593,7 +558,7 @@ public class KLALBController {
|
|||||||
return kr.getRemoteVaddr().getAddress().toInet6Address();
|
return kr.getRemoteVaddr().getAddress().toInet6Address();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target, MultipurposeSocketAddress bind) {
|
private boolean checkContainsTargetAndBind(MultiProtocolSocketAddress target, MultiProtocolSocketAddress bind) {
|
||||||
boolean b = false;
|
boolean b = false;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -609,7 +574,7 @@ public class KLALBController {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkContainsTarget(MultipurposeSocketAddress target) {
|
private boolean checkContainsTarget(MultiProtocolSocketAddress target) {
|
||||||
boolean b = false;
|
boolean b = false;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -639,8 +604,8 @@ public class KLALBController {
|
|||||||
|
|
||||||
private String generateSelfLineTable() {
|
private String generateSelfLineTable() {
|
||||||
StringBuilder sbd = new StringBuilder();
|
StringBuilder sbd = new StringBuilder();
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) {
|
for (Iterator<MultiProtocolSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) {
|
||||||
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next();
|
MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
|
||||||
sbd.append(klalbRemoteLine.toString());
|
sbd.append(klalbRemoteLine.toString());
|
||||||
sbd.append('\n');
|
sbd.append('\n');
|
||||||
}
|
}
|
||||||
@@ -668,7 +633,12 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
|
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
|
||||||
|
networkInterfaceManager.getInetAddressesExcept().add(selfx.getAddress().toInet6Address());
|
||||||
srv6Router = new SRv6Router(selfx, clock);
|
srv6Router = new SRv6Router(selfx, clock);
|
||||||
|
if(configItem!=null) {
|
||||||
|
srv6Router.setPerformanceStrategy(PerformanceStrategy.fromDescription(configItem.getPerformanceStrategy()));
|
||||||
|
srv6Router.setDeviceName(configItem.getDeviceName());
|
||||||
|
}
|
||||||
srv6Router.runKLALBRouteProtocol();
|
srv6Router.runKLALBRouteProtocol();
|
||||||
routingProtocol = srv6Router.getKlalbRouteProtol();
|
routingProtocol = srv6Router.getKlalbRouteProtol();
|
||||||
routingProtocol.addReceiver((addr, packet) -> {
|
routingProtocol.addReceiver((addr, packet) -> {
|
||||||
@@ -678,10 +648,11 @@ public class KLALBController {
|
|||||||
System.out.println(" Loaded: SRv6 Stack");
|
System.out.println(" Loaded: SRv6 Stack");
|
||||||
|
|
||||||
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6;
|
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6;
|
||||||
if (enableVirtualAdapter&&(name!=null)) {
|
boolean enableTUN = enableVirtualAdapter && (name != null) && !name.trim().isEmpty() && !name.trim().equalsIgnoreCase("null");
|
||||||
|
if (enableTUN) {
|
||||||
Thread t=new Thread(()->{
|
Thread t=new Thread(()->{
|
||||||
try {
|
try {
|
||||||
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name,
|
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name.trim(),
|
||||||
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
|
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
|
||||||
tunlink.setMonitor(datatMonitor);
|
tunlink.setMonitor(datatMonitor);
|
||||||
// srv6Router.getLinkTabel().add(tunlink);
|
// srv6Router.getLinkTabel().add(tunlink);
|
||||||
@@ -695,7 +666,7 @@ public class KLALBController {
|
|||||||
});
|
});
|
||||||
t.start();
|
t.start();
|
||||||
}else {
|
}else {
|
||||||
System.out.println(" Tun Adapter Disbaled");
|
System.out.println(" Tun Adapter Disabled");
|
||||||
|
|
||||||
}
|
}
|
||||||
udpr=new UDPProtocolRegister(this);
|
udpr=new UDPProtocolRegister(this);
|
||||||
@@ -715,7 +686,7 @@ public class KLALBController {
|
|||||||
// System.out.println(context);
|
// System.out.println(context);
|
||||||
try {
|
try {
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context,
|
NTPv4Protocol nvc = new NTPv4Protocol(context,
|
||||||
new MultipurposeSocketAddress("{" + iproxyname + "_Datagram}0.0.0.0:123"));
|
new MultiProtocolSocketAddress(iproxyname + "_Datagram","0.0.0.0",123));
|
||||||
srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
|
srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -726,7 +697,7 @@ public class KLALBController {
|
|||||||
for (Neighbor neighbor : nb) {
|
for (Neighbor neighbor : nb) {
|
||||||
if (neighbor.getLocator() != null)
|
if (neighbor.getLocator() != null)
|
||||||
sp.add(new NTPPeer(
|
sp.add(new NTPPeer(
|
||||||
new MultipurposeSocketAddress(iproxyname + "_Datagram",
|
new MultiProtocolSocketAddress(iproxyname + "_Datagram",
|
||||||
neighbor.getLocator().getAddress().toString(), 123),
|
neighbor.getLocator().getAddress().toString(), 123),
|
||||||
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|
||||||
@@ -747,7 +718,7 @@ public class KLALBController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nvc2 = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
nvc2 = new NTPv4Protocol(context, new MultiProtocolSocketAddress("udp","0.0.0.0",0));
|
||||||
|
|
||||||
System.out.println(" Loaded: NTP Module");
|
System.out.println(" Loaded: NTP Module");
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
@@ -764,7 +735,6 @@ public class KLALBController {
|
|||||||
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
|
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
|
||||||
twk.schedule(tsk1, 5000, 5000);
|
twk.schedule(tsk1, 5000, 5000);
|
||||||
twk.schedule(tsk2, 5000, 5000);
|
twk.schedule(tsk2, 5000, 5000);
|
||||||
twk.schedule(tsk3, 5000, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBController() {
|
public KLALBController() {
|
||||||
@@ -830,18 +800,18 @@ public class KLALBController {
|
|||||||
getIpv6Router().setASN(vasn);
|
getIpv6Router().setASN(vasn);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> linele = configItem.getLineTable();
|
List<MultiProtocolSocketAddress> linele = configItem.getLineTable();
|
||||||
if (linele != null) {
|
if (linele != null) {
|
||||||
getSelflineTable().addAll(linele);
|
getSelflineTable().addAll(linele);
|
||||||
}
|
}
|
||||||
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable();
|
List<MultiProtocolSocketAddress> linetoc = configItem.getConnectLineTable();
|
||||||
if (linetoc != null) {
|
if (linetoc != null) {
|
||||||
linetoc.forEach((aline) -> {
|
linetoc.forEach((aline) -> {
|
||||||
addRemoteLines(aline);
|
addRemoteLines(aline);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> ntps = configItem.getNtpServerTable();
|
List<MultiProtocolSocketAddress> ntps = configItem.getNtpServerTable();
|
||||||
if (ntps != null) {
|
if (ntps != null) {
|
||||||
getNTPTable().addAll(ntps);
|
getNTPTable().addAll(ntps);
|
||||||
}
|
}
|
||||||
@@ -1004,24 +974,19 @@ public class KLALBController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> listens = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
public void registerToProxyTypeAs(String proxyname) {
|
public void registerToProxyTypeAs(String proxyname) {
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
||||||
// ProxyProfileEntry.getRegister().put(proxyname, this);
|
// ProxyProfileEntry.getRegister().put(proxyname, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getListenSocketAddress() {
|
private List<MultiProtocolSocketAddress> ntptable = new CopyOnWriteArrayList();
|
||||||
return listens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> ntptable = new CopyOnWriteArrayList();
|
|
||||||
|
|
||||||
private KLALBRoutingProtocol routingProtocol;
|
private KLALBRoutingProtocol routingProtocol;
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getNTPTable() {
|
public List<MultiProtocolSocketAddress> getNTPTable() {
|
||||||
return ntptable;
|
return ntptable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,31 +3,60 @@ package org.kne.cloud.network.klalb;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class KLALBControllerConfigItem extends KLALBConfigItem {
|
public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||||
private String language;
|
private String language;
|
||||||
private boolean nogui;
|
private boolean nogui;
|
||||||
private String VirtualAddress;
|
private String VirtualAddress=KLALBUtils.randomKLALBIPv6Address().toString();
|
||||||
private Long VirtualASN;
|
private Long VirtualASN;
|
||||||
private List<InetAddress> DNS;
|
private List<InetAddress> DNS;
|
||||||
private MultipurposeSocketAddress TCPListen;
|
private MultiProtocolSocketAddress TCPListen=new MultiProtocolSocketAddress("0.0.0.0",4565);
|
||||||
private MultipurposeSocketAddress UDPListen;
|
private MultiProtocolSocketAddress UDPListen=new MultiProtocolSocketAddress("udp","0.0.0.0",4565);
|
||||||
private String VirtualSocketName;
|
private String VirtualSocketName;
|
||||||
private List<MultipurposeSocketAddress>LineTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress>LineTable=new ArrayList<>();
|
||||||
private List<MultipurposeSocketAddress>ConnectLineTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress>ConnectLineTable=new ArrayList<>();
|
||||||
private List<MultipurposeSocketAddress>ntpServerTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress>ntpServerTable=new ArrayList<>();
|
||||||
|
private List<String>ExtraRoutes=new ArrayList<>();
|
||||||
private boolean denyLineTableQuery=false;
|
private boolean denyLineTableQuery=false;
|
||||||
private boolean denyLineTableBroadcast=false;
|
private boolean denyLineTableBroadcast=false;
|
||||||
private String congestionAlgorithm="BBR";
|
private String congestionAlgorithm="BBR";
|
||||||
private double burstLimit=1.20;
|
private double burstLimit=1.50;
|
||||||
private double delayUpperBound=1.20;
|
private double delayUpperBound=1.20;
|
||||||
private double delayLowerBound=1.15;
|
private double delayLowerBound=1.15;
|
||||||
private long nagleDelayTime=1000000L;
|
private long nagleDelayTime=1000000L;
|
||||||
private long linkNagleDelayTime=1000000L;
|
private long linkNagleDelayTime=1000000L;
|
||||||
private int linkConnectionsCount=1;
|
private int linkConnectionsCount=1;
|
||||||
private String TUNName=CONST.KLALB_S_RV6;
|
private String TUNName=CONST.KLALB_S_RV6;
|
||||||
|
private String performanceStrategy="multifill";
|
||||||
|
private String DeviceName;
|
||||||
|
private String DeviceDescription;
|
||||||
|
|
||||||
|
public String getPerformanceStrategy() {
|
||||||
|
return performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPerformanceStrategy(String performanceStrategy) {
|
||||||
|
this.performanceStrategy = performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return DeviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
DeviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceDescription() {
|
||||||
|
return DeviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceDescription(String deviceDescription) {
|
||||||
|
DeviceDescription = deviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
|
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
|
||||||
NetworkInterfaceExcepts = networkInterfaceExcepts;
|
NetworkInterfaceExcepts = networkInterfaceExcepts;
|
||||||
@@ -52,8 +81,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public KLALBControllerConfigItem(String type) {
|
public KLALBControllerConfigItem() {
|
||||||
super(type);
|
super("KLALBController");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -124,65 +153,73 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTCPListen() {
|
public MultiProtocolSocketAddress getTCPListen() {
|
||||||
return TCPListen;
|
return TCPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setTCPListen(MultipurposeSocketAddress tCPListen) {
|
public void setTCPListen(MultiProtocolSocketAddress tCPListen) {
|
||||||
TCPListen = tCPListen;
|
TCPListen = tCPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getUDPListen() {
|
public MultiProtocolSocketAddress getUDPListen() {
|
||||||
return UDPListen;
|
return UDPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setUDPListen(MultipurposeSocketAddress uDPListen) {
|
public void setUDPListen(MultiProtocolSocketAddress uDPListen) {
|
||||||
UDPListen = uDPListen;
|
UDPListen = uDPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getLineTable() {
|
public List<MultiProtocolSocketAddress> getLineTable() {
|
||||||
return LineTable;
|
return LineTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setLineTable(List<MultipurposeSocketAddress> lineTable) {
|
public void setLineTable(List<MultiProtocolSocketAddress> lineTable) {
|
||||||
LineTable = lineTable;
|
LineTable = lineTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getConnectLineTable() {
|
public List<MultiProtocolSocketAddress> getConnectLineTable() {
|
||||||
return ConnectLineTable;
|
return ConnectLineTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setConnectLineTable(List<MultipurposeSocketAddress> connectLineTable) {
|
public void setConnectLineTable(List<MultiProtocolSocketAddress> connectLineTable) {
|
||||||
ConnectLineTable = connectLineTable;
|
ConnectLineTable = connectLineTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getNtpServerTable() {
|
public List<MultiProtocolSocketAddress> getNtpServerTable() {
|
||||||
return ntpServerTable;
|
return ntpServerTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setNtpServerTable(List<MultipurposeSocketAddress> ntpServerTable) {
|
public void setNtpServerTable(List<MultiProtocolSocketAddress> ntpServerTable) {
|
||||||
this.ntpServerTable = ntpServerTable;
|
this.ntpServerTable = ntpServerTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getExtraRoutes() {
|
||||||
|
return ExtraRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtraRoutes(List<String> extraRoutes) {
|
||||||
|
ExtraRoutes = extraRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -306,146 +343,46 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "KLALBControllerConfigItem [language=" + language + ", nogui=" + nogui + ", VirtualAddress="
|
return "KLALBControllerConfigItem{" +
|
||||||
+ VirtualAddress + ", VirtualASN=" + VirtualASN + ", DNS=" + DNS + ", TCPListen=" + TCPListen
|
"language='" + language + '\'' +
|
||||||
+ ", UDPListen=" + UDPListen + ", VirtualSocketName=" + VirtualSocketName + ", LineTable=" + LineTable
|
", nogui=" + nogui +
|
||||||
+ ", ConnectLineTable=" + ConnectLineTable + ", ntpServerTable=" + ntpServerTable
|
", VirtualAddress='" + VirtualAddress + '\'' +
|
||||||
+ ", denyLineTableQuery=" + denyLineTableQuery + ", denyLineTableBroadcast=" + denyLineTableBroadcast
|
", VirtualASN=" + VirtualASN +
|
||||||
+ ", congestionAlgorithm=" + congestionAlgorithm + ", delayUpperBound=" + delayUpperBound
|
", DNS=" + DNS +
|
||||||
+ ", delayLowerBound=" + delayLowerBound + ", nagleDelayTime=" + nagleDelayTime
|
", TCPListen=" + TCPListen +
|
||||||
+ ", linkNagleDelayTime=" + linkNagleDelayTime + ", linkConnectionsCount=" + linkConnectionsCount
|
", UDPListen=" + UDPListen +
|
||||||
+ ", TUNName=" + TUNName + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + "]";
|
", VirtualSocketName='" + VirtualSocketName + '\'' +
|
||||||
|
", LineTable=" + LineTable +
|
||||||
|
", ConnectLineTable=" + ConnectLineTable +
|
||||||
|
", ntpServerTable=" + ntpServerTable +
|
||||||
|
", ExtraRoutes=" + ExtraRoutes +
|
||||||
|
", denyLineTableQuery=" + denyLineTableQuery +
|
||||||
|
", denyLineTableBroadcast=" + denyLineTableBroadcast +
|
||||||
|
", congestionAlgorithm='" + congestionAlgorithm + '\'' +
|
||||||
|
", burstLimit=" + burstLimit +
|
||||||
|
", delayUpperBound=" + delayUpperBound +
|
||||||
|
", delayLowerBound=" + delayLowerBound +
|
||||||
|
", nagleDelayTime=" + nagleDelayTime +
|
||||||
|
", linkNagleDelayTime=" + linkNagleDelayTime +
|
||||||
|
", linkConnectionsCount=" + linkConnectionsCount +
|
||||||
|
", TUNName='" + TUNName + '\'' +
|
||||||
|
", performanceStrategy='" + performanceStrategy + '\'' +
|
||||||
|
", DeviceName='" + DeviceName + '\'' +
|
||||||
|
", DeviceDescription='" + DeviceDescription + '\'' +
|
||||||
|
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
KLALBControllerConfigItem that = (KLALBControllerConfigItem) o;
|
||||||
|
return nogui == that.nogui && denyLineTableQuery == that.denyLineTableQuery && denyLineTableBroadcast == that.denyLineTableBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(LineTable, that.LineTable) && Objects.equals(ConnectLineTable, that.ConnectLineTable) && Objects.equals(ntpServerTable, that.ntpServerTable) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, LineTable, ConnectLineTable, ntpServerTable, ExtraRoutes, denyLineTableQuery, denyLineTableBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts);
|
||||||
int result = super.hashCode();
|
|
||||||
result = prime * result + ((ConnectLineTable == null) ? 0 : ConnectLineTable.hashCode());
|
|
||||||
result = prime * result + ((DNS == null) ? 0 : DNS.hashCode());
|
|
||||||
result = prime * result + ((LineTable == null) ? 0 : LineTable.hashCode());
|
|
||||||
result = prime * result + ((NetworkInterfaceExcepts == null) ? 0 : NetworkInterfaceExcepts.hashCode());
|
|
||||||
result = prime * result + ((TCPListen == null) ? 0 : TCPListen.hashCode());
|
|
||||||
result = prime * result + ((TUNName == null) ? 0 : TUNName.hashCode());
|
|
||||||
result = prime * result + ((UDPListen == null) ? 0 : UDPListen.hashCode());
|
|
||||||
result = prime * result + ((VirtualASN == null) ? 0 : VirtualASN.hashCode());
|
|
||||||
result = prime * result + ((VirtualAddress == null) ? 0 : VirtualAddress.hashCode());
|
|
||||||
result = prime * result + ((VirtualSocketName == null) ? 0 : VirtualSocketName.hashCode());
|
|
||||||
result = prime * result + ((congestionAlgorithm == null) ? 0 : congestionAlgorithm.hashCode());
|
|
||||||
long temp;
|
|
||||||
temp = Double.doubleToLongBits(delayLowerBound);
|
|
||||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
|
||||||
temp = Double.doubleToLongBits(delayUpperBound);
|
|
||||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
|
||||||
result = prime * result + (denyLineTableBroadcast ? 1231 : 1237);
|
|
||||||
result = prime * result + (denyLineTableQuery ? 1231 : 1237);
|
|
||||||
result = prime * result + ((language == null) ? 0 : language.hashCode());
|
|
||||||
result = prime * result + linkConnectionsCount;
|
|
||||||
result = prime * result + (int) (linkNagleDelayTime ^ (linkNagleDelayTime >>> 32));
|
|
||||||
result = prime * result + (int) (nagleDelayTime ^ (nagleDelayTime >>> 32));
|
|
||||||
result = prime * result + (nogui ? 1231 : 1237);
|
|
||||||
result = prime * result + ((ntpServerTable == null) ? 0 : ntpServerTable.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj)
|
|
||||||
return true;
|
|
||||||
if (!super.equals(obj))
|
|
||||||
return false;
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
return false;
|
|
||||||
KLALBControllerConfigItem other = (KLALBControllerConfigItem) obj;
|
|
||||||
if (ConnectLineTable == null) {
|
|
||||||
if (other.ConnectLineTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!ConnectLineTable.equals(other.ConnectLineTable))
|
|
||||||
return false;
|
|
||||||
if (DNS == null) {
|
|
||||||
if (other.DNS != null)
|
|
||||||
return false;
|
|
||||||
} else if (!DNS.equals(other.DNS))
|
|
||||||
return false;
|
|
||||||
if (LineTable == null) {
|
|
||||||
if (other.LineTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!LineTable.equals(other.LineTable))
|
|
||||||
return false;
|
|
||||||
if (NetworkInterfaceExcepts == null) {
|
|
||||||
if (other.NetworkInterfaceExcepts != null)
|
|
||||||
return false;
|
|
||||||
} else if (!NetworkInterfaceExcepts.equals(other.NetworkInterfaceExcepts))
|
|
||||||
return false;
|
|
||||||
if (TCPListen == null) {
|
|
||||||
if (other.TCPListen != null)
|
|
||||||
return false;
|
|
||||||
} else if (!TCPListen.equals(other.TCPListen))
|
|
||||||
return false;
|
|
||||||
if (TUNName == null) {
|
|
||||||
if (other.TUNName != null)
|
|
||||||
return false;
|
|
||||||
} else if (!TUNName.equals(other.TUNName))
|
|
||||||
return false;
|
|
||||||
if (UDPListen == null) {
|
|
||||||
if (other.UDPListen != null)
|
|
||||||
return false;
|
|
||||||
} else if (!UDPListen.equals(other.UDPListen))
|
|
||||||
return false;
|
|
||||||
if (VirtualASN == null) {
|
|
||||||
if (other.VirtualASN != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualASN.equals(other.VirtualASN))
|
|
||||||
return false;
|
|
||||||
if (VirtualAddress == null) {
|
|
||||||
if (other.VirtualAddress != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualAddress.equals(other.VirtualAddress))
|
|
||||||
return false;
|
|
||||||
if (VirtualSocketName == null) {
|
|
||||||
if (other.VirtualSocketName != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualSocketName.equals(other.VirtualSocketName))
|
|
||||||
return false;
|
|
||||||
if (congestionAlgorithm == null) {
|
|
||||||
if (other.congestionAlgorithm != null)
|
|
||||||
return false;
|
|
||||||
} else if (!congestionAlgorithm.equals(other.congestionAlgorithm))
|
|
||||||
return false;
|
|
||||||
if (Double.doubleToLongBits(delayLowerBound) != Double.doubleToLongBits(other.delayLowerBound))
|
|
||||||
return false;
|
|
||||||
if (Double.doubleToLongBits(delayUpperBound) != Double.doubleToLongBits(other.delayUpperBound))
|
|
||||||
return false;
|
|
||||||
if (denyLineTableBroadcast != other.denyLineTableBroadcast)
|
|
||||||
return false;
|
|
||||||
if (denyLineTableQuery != other.denyLineTableQuery)
|
|
||||||
return false;
|
|
||||||
if (language == null) {
|
|
||||||
if (other.language != null)
|
|
||||||
return false;
|
|
||||||
} else if (!language.equals(other.language))
|
|
||||||
return false;
|
|
||||||
if (linkConnectionsCount != other.linkConnectionsCount)
|
|
||||||
return false;
|
|
||||||
if (linkNagleDelayTime != other.linkNagleDelayTime)
|
|
||||||
return false;
|
|
||||||
if (nagleDelayTime != other.nagleDelayTime)
|
|
||||||
return false;
|
|
||||||
if (nogui != other.nogui)
|
|
||||||
return false;
|
|
||||||
if (ntpServerTable == null) {
|
|
||||||
if (other.ntpServerTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!ntpServerTable.equals(other.ntpServerTable))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -11,7 +13,7 @@ import java.util.List;
|
|||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SocketChannelListener;
|
import org.kne.cloud.network.SocketChannelListener;
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
import org.kne.cloud.network.ipv6.RouteItem;
|
import org.kne.cloud.network.ipv6.RouteItem;
|
||||||
@@ -87,6 +89,7 @@ public class KLALBMain {
|
|||||||
System.out.println(" links-remove <addr:port>: remove link");
|
System.out.println(" links-remove <addr:port>: remove link");
|
||||||
System.out.println(" links-reconnect: reconnect all link");
|
System.out.println(" links-reconnect: reconnect all link");
|
||||||
System.out.println(" kltp-state: query kltp states");
|
System.out.println(" kltp-state: query kltp states");
|
||||||
|
System.out.println(" interfaces: show all network interfaces");
|
||||||
System.out.println(" route: display internal route table");
|
System.out.println(" route: display internal route table");
|
||||||
System.out.println(" kperf <addr:port>: performance benchmark");
|
System.out.println(" kperf <addr:port>: performance benchmark");
|
||||||
System.out.println(" exit: Exit");
|
System.out.println(" exit: Exit");
|
||||||
@@ -115,7 +118,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "links-add":
|
case "links-add":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
|
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
|
||||||
if(addl.isEmpty()) {
|
if(addl.isEmpty()) {
|
||||||
//System.out.println("添加失败,线路已存在!");
|
//System.out.println("添加失败,线路已存在!");
|
||||||
@@ -135,7 +138,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "links-remove":
|
case "links-remove":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
|
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
|
||||||
if(rmvl.isEmpty()) {
|
if(rmvl.isEmpty()) {
|
||||||
System.out.println("No matched link has been found.");
|
System.out.println("No matched link has been found.");
|
||||||
@@ -161,6 +164,18 @@ public class KLALBMain {
|
|||||||
case "kltp-state":
|
case "kltp-state":
|
||||||
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
|
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
|
||||||
break;
|
break;
|
||||||
|
case "interfaces":
|
||||||
|
NetworkInterfaceManager networkInterfaceManager= kpcje.getKlalbController().getNetworkInterfaceManager();
|
||||||
|
List<NetworkInterface> interfaceList=networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||||
|
System.out.println("Network interface detected:");
|
||||||
|
for(NetworkInterface networkInterface:interfaceList){
|
||||||
|
System.out.println(networkInterface.getName()+"("+networkInterface.getDisplayName()+")");
|
||||||
|
List<InetAddress> lst= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||||
|
for(InetAddress address :lst){
|
||||||
|
System.out.println(" "+address.getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "route":
|
case "route":
|
||||||
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
|
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
|
||||||
Collections.sort(lri);
|
Collections.sort(lri);
|
||||||
@@ -174,7 +189,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "kperf":
|
case "kperf":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
Kperf kp=new Kperf(mpsa);
|
Kperf kp=new Kperf(mpsa);
|
||||||
kp.startPerfing();
|
kp.startPerfing();
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.LongAdder;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
public interface KLALBPacketLink {
|
public interface KLALBPacketLink {
|
||||||
public void writeKLALBPacket(KLALBPacket kp) throws IOException;
|
public void writeKLALBPacket(KLALBPacket kp) throws IOException;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.net.UnknownHostException;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.KLALBDetectorItem;
|
import org.kne.cloud.network.KLALBDetectorItem;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ProtocolDetector;
|
import org.kne.cloud.network.ProtocolDetector;
|
||||||
import org.kne.cloud.network.ProtocolDetectorServerSocket;
|
import org.kne.cloud.network.ProtocolDetectorServerSocket;
|
||||||
import org.kne.cloud.network.ProtocolDetectorSocket;
|
import org.kne.cloud.network.ProtocolDetectorSocket;
|
||||||
@@ -30,8 +30,8 @@ public class KLALBProtocolDetectSocketListener extends SocketListener {
|
|||||||
super.open();
|
super.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException {
|
public KLALBProtocolDetectSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress, KLALBController klalbc) throws IOException {
|
||||||
super(multipurposeSocketAddress);
|
super(multiProtocolSocketAddress);
|
||||||
this.klalbController=klalbc;
|
this.klalbController=klalbc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class KLALBProxySystem {
|
|||||||
private File jsonFile;
|
private File jsonFile;
|
||||||
{
|
{
|
||||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||||
gson=gb.create();
|
gson=gb.create();
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableRemoteManagement(MultipurposeSocketAddress bind) throws IOException {
|
public void enableRemoteManagement(MultiProtocolSocketAddress bind) throws IOException {
|
||||||
if(krm==null) {
|
if(krm==null) {
|
||||||
krm=new KLALBRemoteManagement(this,bind);
|
krm=new KLALBRemoteManagement(this,bind);
|
||||||
}else {
|
}else {
|
||||||
@@ -84,13 +84,17 @@ public class KLALBProxySystem {
|
|||||||
|
|
||||||
public void loadConfigJson(File jsonFile) throws IOException {
|
public void loadConfigJson(File jsonFile) throws IOException {
|
||||||
this.jsonFile=jsonFile;
|
this.jsonFile=jsonFile;
|
||||||
FileReader fr = null;
|
if(jsonFile.exists()) {
|
||||||
try {
|
FileReader fr = null;
|
||||||
fr=new FileReader(jsonFile);
|
try {
|
||||||
loadConfigJson(fr);
|
fr = new FileReader(jsonFile);
|
||||||
}finally {
|
loadConfigJson(fr);
|
||||||
if(fr!=null)
|
} finally {
|
||||||
fr.close();
|
if (fr != null)
|
||||||
|
fr.close();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
loadConfig(KLALBConfig.getDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -128,9 +132,9 @@ public class KLALBProxySystem {
|
|||||||
|
|
||||||
String vsne=kcci.getVirtualSocketName();
|
String vsne=kcci.getVirtualSocketName();
|
||||||
//if(vsne!=null) {
|
//if(vsne!=null) {
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
||||||
//}
|
//}
|
||||||
MultipurposeSocketAddress tcple=kcci.getTCPListen();
|
MultiProtocolSocketAddress tcple=kcci.getTCPListen();
|
||||||
if(tcple!=null) {
|
if(tcple!=null) {
|
||||||
|
|
||||||
SocketChannelListener tcpl = null;
|
SocketChannelListener tcpl = null;
|
||||||
@@ -148,13 +152,13 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(tcple.getType(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
MultiProtocolSocketAddress mpsa2=new MultiProtocolSocketAddress(tcple.getProtocol(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
||||||
klalbController.getListenSocketAddress().add(mpsa2);
|
klalbController.getListenSocketAddress().add(mpsa2);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MultipurposeSocketAddress udple=kcci.getUDPListen();
|
MultiProtocolSocketAddress udple=kcci.getUDPListen();
|
||||||
if(udple!=null) {
|
if(udple!=null) {
|
||||||
DatagramSocketListener udpl = null;
|
DatagramSocketListener udpl = null;
|
||||||
try {
|
try {
|
||||||
@@ -171,7 +175,7 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
MultipurposeSocketAddress mpsau2=new MultipurposeSocketAddress(udple.getType(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
MultiProtocolSocketAddress mpsau2=new MultiProtocolSocketAddress(udple.getProtocol(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
||||||
//klalbController.getListenSocketAddress().add(mpsau2);
|
//klalbController.getListenSocketAddress().add(mpsau2);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
|
import java.net.BindException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -21,7 +24,7 @@ import org.kne.cloud.clock.HighAccuracyClock;
|
|||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
|
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
|
||||||
import org.kne.cloud.clock.WatchDogTimer;
|
import org.kne.cloud.clock.WatchDogTimer;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
|
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
|
||||||
import org.kne.cloud.network.congestion.CongestionAlgorithm;
|
import org.kne.cloud.network.congestion.CongestionAlgorithm;
|
||||||
@@ -100,8 +103,8 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
private LinkStatus status=new LinkStatus();
|
private LinkStatus status=new LinkStatus();
|
||||||
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
|
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
|
||||||
private volatile KLALBPacketLink kplink;
|
private volatile KLALBPacketLink kplink;
|
||||||
private MultipurposeSocketAddress bindAddress;
|
private MultiProtocolSocketAddress bindAddress;
|
||||||
private MultipurposeSocketAddress socketAddress;
|
private MultiProtocolSocketAddress socketAddress;
|
||||||
|
|
||||||
|
|
||||||
public void setKplink(KLALBPacketLink kplink) {
|
public void setKplink(KLALBPacketLink kplink) {
|
||||||
@@ -112,11 +115,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
return kplink;
|
return kplink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getSocketAddress() {
|
public MultiProtocolSocketAddress getSocketAddress() {
|
||||||
return socketAddress;
|
return socketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getBindAddress() {
|
public MultiProtocolSocketAddress getBindAddress() {
|
||||||
return bindAddress;
|
return bindAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +137,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
return peerAddress;
|
return peerAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller, MultipurposeSocketAddress mpa, IPv6AddressGroup address) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
|
||||||
this(controller,mpa, null,address);
|
this(controller,mpa, null,address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
name=kpl.toString();
|
name=kpl.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr, IPv6AddressGroup address) {
|
private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
|
||||||
this.klalbController=controller;
|
this.klalbController=controller;
|
||||||
sysclk = klalbController.getClock();
|
sysclk = klalbController.getClock();
|
||||||
this.socketAddress = mpa;
|
this.socketAddress = mpa;
|
||||||
@@ -162,11 +165,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bind) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
|
||||||
this(controller,mpa, bind, null);
|
this(controller,mpa, bind, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
|
||||||
this(controller,mpa, (IPv6AddressGroup) null);
|
this(controller,mpa, (IPv6AddressGroup) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +286,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
sendLock.unlock();
|
sendLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlock.parkNanos(1000000L);
|
tlock.parkNanos(10000000L);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (StreamCorruptedException sce) {
|
} catch (StreamCorruptedException sce) {
|
||||||
@@ -464,16 +467,18 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
break;
|
break;
|
||||||
case KLALBPacket.VADDR:
|
case KLALBPacket.VADDR:
|
||||||
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
|
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
|
||||||
|
if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
|
||||||
remoteVaddr = vdr;
|
close();
|
||||||
sendPacket(new VADDRACKPacket());
|
}else {
|
||||||
if (fst.compareAndSet(true, false)) {
|
remoteVaddr = vdr;
|
||||||
// coll.resetCoolingTime();
|
sendPacket(new VADDRACKPacket());
|
||||||
status.setState(LinkStatus.UP);
|
if (fst.compareAndSet(true, false)) {
|
||||||
KLALBRemoteLink.this.onOnlineStateUpdate();
|
// coll.resetCoolingTime();
|
||||||
|
status.setState(LinkStatus.UP);
|
||||||
|
KLALBRemoteLink.this.onOnlineStateUpdate();
|
||||||
|
}
|
||||||
|
KLALBRemoteLink.this.onLocatorUpdate();
|
||||||
}
|
}
|
||||||
KLALBRemoteLink.this.onLocatorUpdate();
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case KLALBPacket.VADDRACK:
|
case KLALBPacket.VADDRACK:
|
||||||
break;
|
break;
|
||||||
@@ -567,7 +572,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
public void run() {
|
public void run() {
|
||||||
while (!closed) {
|
while (!closed) {
|
||||||
status.updateReliability();
|
status.updateReliability();
|
||||||
monitor.update();
|
if(status.getState()!=LinkStatus.DOWN) {
|
||||||
|
monitor.update();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -672,7 +679,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
}
|
}
|
||||||
} catch (StreamCorruptedException sce) {
|
} catch (StreamCorruptedException sce) {
|
||||||
sce.printStackTrace();
|
sce.printStackTrace();
|
||||||
} catch (IOException | UnresolvedAddressException e) {
|
}catch(BindException e) {
|
||||||
|
close();
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
}catch(NoRouteToHostException e){
|
||||||
|
close();
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch(SocketException e) {
|
||||||
|
String massage = e.getMessage();
|
||||||
|
if (massage.toLowerCase().contains("unreachable")) {
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}catch (IOException | UnresolvedAddressException e) {
|
||||||
if (debug)
|
if (debug)
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -927,23 +950,25 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
public List<RouteItem> getRouteItems() {
|
public List<RouteItem> getRouteItems() {
|
||||||
List<RouteItem> rlist = new ArrayList<>();
|
List<RouteItem> rlist = new ArrayList<>();
|
||||||
|
|
||||||
|
if(isUp()) {
|
||||||
IPv6AddressGroup adg = addressGroup;
|
IPv6AddressGroup adg = addressGroup;
|
||||||
/*if (adg != null)
|
/*if (adg != null)
|
||||||
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
|
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
|
||||||
null, "D", true));*/
|
null, "D", true));*/
|
||||||
|
|
||||||
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
|
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
|
||||||
Neighbor addresses = (Neighbor) iteratorx.next();
|
Neighbor addresses = (Neighbor) iteratorx.next();
|
||||||
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
|
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
|
||||||
addresses.getAddress().getAddress(), this, "Direct", 0, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
addresses.getAddress().getAddress(), this, "Direct", 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||||
rlist.add(ri);
|
rlist.add(ri);
|
||||||
|
|
||||||
if (addresses.getLocator() != null) {
|
if (addresses.getLocator() != null) {
|
||||||
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
|
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
|
||||||
"KLALB SRv6", 13, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
"KLALB SRv6", 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||||
rlist.add(ris);
|
rlist.add(ris);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rlist;
|
return rlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SocketListener;
|
import org.kne.cloud.network.SocketListener;
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
import org.kne.cloud.network.monitor.LinkStatus;
|
import org.kne.cloud.network.monitor.LinkStatus;
|
||||||
@@ -20,9 +20,9 @@ public class KLALBRemoteManagement {
|
|||||||
SocketListener slr;
|
SocketListener slr;
|
||||||
private KLALBProxySystem klalbProxySystem;
|
private KLALBProxySystem klalbProxySystem;
|
||||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem) throws IOException {
|
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem) throws IOException {
|
||||||
this(klalbProxySystem,new MultipurposeSocketAddress("127.9.9.9", 49573));
|
this(klalbProxySystem,new MultiProtocolSocketAddress("127.9.9.9", 49573));
|
||||||
}
|
}
|
||||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem,MultipurposeSocketAddress listen) throws IOException {
|
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem, MultiProtocolSocketAddress listen) throws IOException {
|
||||||
this.klalbProxySystem=klalbProxySystem;
|
this.klalbProxySystem=klalbProxySystem;
|
||||||
slr=new SocketListener(listen);
|
slr=new SocketListener(listen);
|
||||||
slr.setCon((srcv)->{
|
slr.setCon((srcv)->{
|
||||||
@@ -97,18 +97,18 @@ public class KLALBRemoteManagement {
|
|||||||
String mip=jreq.getAsJsonPrimitive("ipport").getAsString();
|
String mip=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
jrsp.addProperty ("Vaddr",klalbProxySystem.getKlalbController().getRemoteVaddrBySocketAddress(new MultipurposeSocketAddress(mip)).getHostAddress());
|
jrsp.addProperty ("Vaddr",klalbProxySystem.getKlalbController().getRemoteVaddrBySocketAddress(new MultiProtocolSocketAddress(mip)).getHostAddress());
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "GETSELFLINES":
|
case "GETSELFLINES":
|
||||||
JsonArray lines2=new JsonArray();
|
JsonArray lines2=new JsonArray();
|
||||||
List<MultipurposeSocketAddress>selflineslist=klalbProxySystem.getKlalbController().getSelflineTable();
|
List<MultiProtocolSocketAddress>selflineslist=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||||
synchronized (selflineslist) {
|
synchronized (selflineslist) {
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineslist.iterator(); iterator.hasNext();) {
|
for (Iterator<MultiProtocolSocketAddress> iterator = selflineslist.iterator(); iterator.hasNext();) {
|
||||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator.next();
|
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator.next();
|
||||||
lines2.add(new JsonPrimitive(multipurposeSocketAddress.toString()));
|
lines2.add(new JsonPrimitive(multiProtocolSocketAddress.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jrsp.add("table", lines2);
|
jrsp.add("table", lines2);
|
||||||
@@ -116,16 +116,16 @@ public class KLALBRemoteManagement {
|
|||||||
break;
|
break;
|
||||||
case "ADDSELFLINE":
|
case "ADDSELFLINE":
|
||||||
String mips=jreq.getAsJsonPrimitive("ipport").getAsString();
|
String mips=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||||
List<MultipurposeSocketAddress>selflineslist2=klalbProxySystem.getKlalbController().getSelflineTable();
|
List<MultiProtocolSocketAddress>selflineslist2=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||||
synchronized (selflineslist2) {
|
synchronized (selflineslist2) {
|
||||||
selflineslist2.add(new MultipurposeSocketAddress(mips));
|
selflineslist2.add(new MultiProtocolSocketAddress(mips));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "REMOVESELFLINE":
|
case "REMOVESELFLINE":
|
||||||
String mipsr=jreq.getAsJsonPrimitive("ipport").getAsString();
|
String mipsr=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||||
List<MultipurposeSocketAddress>selflineslist21=klalbProxySystem.getKlalbController().getSelflineTable();
|
List<MultiProtocolSocketAddress>selflineslist21=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||||
synchronized (selflineslist21) {
|
synchronized (selflineslist21) {
|
||||||
selflineslist21.add(new MultipurposeSocketAddress(mipsr));
|
selflineslist21.add(new MultiProtocolSocketAddress(mipsr));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "GETLINKMONITOR":
|
case "GETLINKMONITOR":
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import org.jctools.counters.CountersFactory;
|
import org.jctools.counters.CountersFactory;
|
||||||
import org.jctools.counters.FixedSizeStripedLongCounter;
|
import org.jctools.counters.FixedSizeStripedLongCounter;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
|
|
||||||
public class KLALBUtils {
|
public class KLALBUtils {
|
||||||
@@ -51,6 +51,27 @@ public class KLALBUtils {
|
|||||||
v[3] = 0x01;
|
v[3] = 0x01;
|
||||||
return IPv6Address.valueOf(v);
|
return IPv6Address.valueOf(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String parseCidr(String cidr) {
|
||||||
|
int idx = cidr.indexOf('/');
|
||||||
|
if (idx <= 0 || idx == cidr.length() - 1)
|
||||||
|
throw new IllegalArgumentException("not a cidr: " + cidr);
|
||||||
|
int prefix;
|
||||||
|
try {
|
||||||
|
prefix = Integer.parseInt(cidr.substring(idx + 1));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("bad prefix: " + cidr);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
InetAddress addr = InetAddress.getByName(cidr.substring(0, idx));
|
||||||
|
int max = (addr instanceof Inet6Address) ? 128 : 32;
|
||||||
|
if (prefix < 0 || prefix > max)
|
||||||
|
throw new IllegalArgumentException("prefix out of range: " + cidr);
|
||||||
|
return addr.getHostAddress() + "/" + prefix;
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new IllegalArgumentException("bad address: " + cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println(uuidToIP(new UUID(-1,-1)));
|
System.out.println(uuidToIP(new UUID(-1,-1)));
|
||||||
}
|
}
|
||||||
@@ -208,7 +229,7 @@ public class KLALBUtils {
|
|||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, MultipurposeSocketAddress targetAddress,boolean buffered)
|
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress, MultiProtocolSocketAddress targetAddress, boolean buffered)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (targetAddress.isStream()) {
|
if (targetAddress.isStream()) {
|
||||||
if (targetAddress.supportNIO()) {
|
if (targetAddress.supportNIO()) {
|
||||||
@@ -235,8 +256,8 @@ public class KLALBUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress,
|
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress,
|
||||||
MultipurposeSocketAddress targetAddress,boolean buffered, int timeout) throws UnknownHostException, IOException {
|
MultiProtocolSocketAddress targetAddress, boolean buffered, int timeout) throws UnknownHostException, IOException {
|
||||||
if (targetAddress.isStream()) {
|
if (targetAddress.isStream()) {
|
||||||
if (targetAddress.supportNIO()) {
|
if (targetAddress.supportNIO()) {
|
||||||
if (bindAddress != null) {
|
if (bindAddress != null) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NetworkInterfaceManager {
|
||||||
|
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
||||||
|
private List<InetAddress> inetAddressesExcept = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||||
|
return networkInterfaceExcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<InetAddress> getInetAddressesExcept() {
|
||||||
|
return inetAddressesExcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NetworkInterface> getAllAvaliableNetworkInterface() throws SocketException {
|
||||||
|
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
|
||||||
|
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (eu.hasMoreElements()) {
|
||||||
|
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
||||||
|
|
||||||
|
if (networkInterface.getDisplayName()
|
||||||
|
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (networkInterfaceExcept.contains(networkInterface)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (networkInterface.isUp()) {
|
||||||
|
interfaces.add(networkInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return interfaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
||||||
|
List<NetworkInterface> interfaces=getAllAvaliableNetworkInterface();
|
||||||
|
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||||
|
for (NetworkInterface nif:interfaces){
|
||||||
|
addresses.addAll( getNetworkInterfaceAddress(nif));
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetAddress> getNetworkInterfaceAddress(NetworkInterface networkInterface) throws SocketException {
|
||||||
|
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||||
|
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
||||||
|
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
||||||
|
while (ei.hasMoreElements()) {
|
||||||
|
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||||
|
|
||||||
|
if (inetAddress.isLoopbackAddress()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inetAddressesExcept.contains( inetAddress)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addresses.add(inetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.klalb.ui.UIEnv;
|
||||||
|
|
||||||
|
public enum PerformanceStrategy {
|
||||||
|
SINGLE_CORE("singlecore"),
|
||||||
|
MULTI_FILL("multifill"),
|
||||||
|
MULTI_SCATTER("multiscatter");
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private PerformanceStrategy(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 description 查找对应的枚举对象。
|
||||||
|
*
|
||||||
|
* @param description 描述文本(资源包key)
|
||||||
|
* @return 对应的枚举对象
|
||||||
|
*/
|
||||||
|
public static PerformanceStrategy fromDescription(String description) {
|
||||||
|
for (PerformanceStrategy strategy : values()) {
|
||||||
|
if (strategy.description.equals(description)) {
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,23 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.channels.MulticastChannel;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
|
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
|
||||||
import org.kne.cloud.network.DefaultSocketBridgeFactory;
|
import org.kne.cloud.network.DefaultSocketBridgeFactory;
|
||||||
import org.kne.cloud.network.HostPortMap;
|
import org.kne.cloud.network.HostPortMap;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.Proxy;
|
import org.kne.cloud.network.Proxy;
|
||||||
import org.kne.cloud.network.SocketBridgeFactory;
|
import org.kne.cloud.network.SocketBridgeFactory;
|
||||||
import org.kne.cloud.network.SocketToSocketProxy;
|
import org.kne.cloud.network.SocketToSocketProxy;
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
public class SocketBridgeConfigItem extends KLALBConfigItem {
|
public class SocketBridgeConfigItem extends KLALBConfigItem {
|
||||||
private MultipurposeSocketAddress Listen;
|
private MultiProtocolSocketAddress Listen;
|
||||||
|
|
||||||
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
|
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
|
||||||
private HostPortMap Connect=new HostPortMap();
|
private HostPortMap Connect=new HostPortMap();
|
||||||
|
|
||||||
public SocketBridgeConfigItem( MultipurposeSocketAddress listen,
|
public SocketBridgeConfigItem( MultiProtocolSocketAddress listen,
|
||||||
LinkedHashMap<String, String> bridge, HostPortMap connect) {
|
LinkedHashMap<String, String> bridge, HostPortMap connect) {
|
||||||
super("SocketBridge");
|
super("SocketBridge");
|
||||||
Listen = listen;
|
Listen = listen;
|
||||||
@@ -85,12 +68,12 @@ public class SocketBridgeConfigItem extends KLALBConfigItem {
|
|||||||
public Proxy createProxy(KLALBController controller) throws IOException {
|
public Proxy createProxy(KLALBController controller) throws IOException {
|
||||||
HostPortMap mapp=new HostPortMap();
|
HostPortMap mapp=new HostPortMap();
|
||||||
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
|
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
|
||||||
MultipurposeSocketAddress l=Listen;
|
MultiProtocolSocketAddress l=Listen;
|
||||||
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
|
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
|
||||||
MultipurposeSocketAddress r=getDefaultConnect(Connect,mapp);
|
MultiProtocolSocketAddress r=getDefaultConnect(Connect,mapp);
|
||||||
return new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
return new SocketToSocketProxy(l ,new MultiProtocolSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
||||||
}
|
}
|
||||||
private MultipurposeSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultipurposeSocketAddress> mapp) {
|
private MultiProtocolSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultiProtocolSocketAddress> mapp) {
|
||||||
connect2.entrySet().forEach((en)->{
|
connect2.entrySet().forEach((en)->{
|
||||||
mapp.put(en.getKey(), en.getValue());
|
mapp.put(en.getKey(), en.getValue());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,29 +116,20 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
|||||||
}*/
|
}*/
|
||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket;
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
|
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.PacketRebuilder;
|
import org.kne.cloud.network.PacketRebuilder;
|
||||||
import org.kne.cloud.network.PacketSpliter;
|
import org.kne.cloud.network.PacketSpliter;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
|
||||||
import org.kne.debug.TimeDebugger;
|
|
||||||
|
|
||||||
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
private PacketSpliter pslr;
|
private PacketSpliter pslr;
|
||||||
@@ -146,7 +137,7 @@ public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink impl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatagramSocket ds;
|
private DatagramSocket ds;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.nio.channels.SocketChannel;
|
|||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.kne.cloud.network.BufferedChannel;
|
import org.kne.cloud.network.BufferedChannel;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.io.KNEChannels;
|
import org.kne.io.KNEChannels;
|
||||||
@@ -45,7 +45,7 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteAddress());
|
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteAddress());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return "?←?";
|
return "?←?";
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Socket connectSocket;
|
private Socket connectSocket;
|
||||||
|
|||||||
@@ -346,7 +346,16 @@ public class GraphPanel extends JPanel {
|
|||||||
g.setFont(UIEnv.getFont().deriveFont(10.0f));
|
g.setFont(UIEnv.getFont().deriveFont(10.0f));
|
||||||
|
|
||||||
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
|
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
|
||||||
g.drawString(text, (int)(x-image.getWidth(null)/2-10), (int)(y+image.getHeight(null)/2+10));
|
int imgw=(image!=null)?image.getWidth(null):(int)nodesize;
|
||||||
|
int imgh=(image!=null)?image.getHeight(null):(int)nodesize;
|
||||||
|
java.awt.FontMetrics fm=g.getFontMetrics();
|
||||||
|
int lineHeight=fm.getHeight()+2;
|
||||||
|
int textx=(int)(x-imgw/2-10);
|
||||||
|
int texty=(int)(y+imgh/2+10)+fm.getAscent();
|
||||||
|
String[] lines=text.split("\n");
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
g.drawString(lines[i], textx, texty+i*lineHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 getPositionVec2() {
|
public Vector2 getPositionVec2() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.jfree.chart.plot.dial.*;
|
|||||||
import org.jfree.data.general.DefaultValueDataset;
|
import org.jfree.data.general.DefaultValueDataset;
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.klalb.uitool.*;
|
import org.kne.cloud.klalb.uitool.*;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
import org.kne.cloud.network.klalb.*;
|
import org.kne.cloud.network.klalb.*;
|
||||||
@@ -80,7 +80,11 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private JTextField textFieldLocate; // 定位地址输入框
|
private JTextField textFieldLocate; // 定位地址输入框
|
||||||
private JTextField asnField2; // ASN显示框
|
private JTextField asnField2; // ASN显示框
|
||||||
private JTextArea dnsAreaSet; // DNS服务器设置区域
|
private JTextArea dnsAreaSet; // DNS服务器设置区域
|
||||||
private JComboBox comboPerformance; // 性能策略选择框
|
private JTextArea extraRoutesSet; // 额外路由设置区域
|
||||||
|
private JTextField deviceNameSet; // 设备名称设置框
|
||||||
|
private JTextArea deviceDescriptionSet; // 设备描述设置区域
|
||||||
|
private KLALBController kcontroller; // 关联的控制器
|
||||||
|
private JComboBox<PerformanceStrategyItem> comboPerformance; // 性能策略选择框
|
||||||
private JComboBox comboCongestion; // 拥塞控制算法选择框
|
private JComboBox comboCongestion; // 拥塞控制算法选择框
|
||||||
private JTextField tcpListeningSet; // TCP监听设置
|
private JTextField tcpListeningSet; // TCP监听设置
|
||||||
private JTextField udpListeningSet; // UDP监听设置
|
private JTextField udpListeningSet; // UDP监听设置
|
||||||
@@ -138,6 +142,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
* 初始化UI界面
|
* 初始化UI界面
|
||||||
*/
|
*/
|
||||||
private void initUI(KLALBController kc, String title) {
|
private void initUI(KLALBController kc, String title) {
|
||||||
|
kcontroller = kc;
|
||||||
// 设置窗口基本属性
|
// 设置窗口基本属性
|
||||||
setIconImage(UIEnv.getIcon());
|
setIconImage(UIEnv.getIcon());
|
||||||
setTitleColor(UIEnv.getDefaultTitleColor());
|
setTitleColor(UIEnv.getDefaultTitleColor());
|
||||||
@@ -597,7 +602,11 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||||
btnNewButton.addActionListener(e -> {
|
btnNewButton.addActionListener(e -> {
|
||||||
try {
|
try {
|
||||||
kc.addRemoteLines(new MultipurposeSocketAddress(textField.getText()));
|
MultiProtocolSocketAddress mpsa= new MultiProtocolSocketAddress(textField.getText());
|
||||||
|
Thread t=new Thread(()->{
|
||||||
|
kc.addRemoteLines(mpsa);
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
} catch (RuntimeException ex) {
|
} catch (RuntimeException ex) {
|
||||||
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
|
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
@@ -781,12 +790,30 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
addressFieldSet = addr6.getTextField();
|
addressFieldSet = addr6.getTextField();
|
||||||
settings.getView().add(addr6);
|
settings.getView().add(addr6);
|
||||||
|
|
||||||
|
// 设备名称设置
|
||||||
|
TextSettingItem deviceName = new TextSettingItem(UIEnv.getRsb().getString("devicename"),
|
||||||
|
CONST.itemwidth, CONST.settingheight);
|
||||||
|
deviceNameSet = deviceName.getTextField();
|
||||||
|
settings.getView().add(deviceName);
|
||||||
|
|
||||||
|
// 设备描述设置
|
||||||
|
TextAreaSettingItem deviceDescription = new TextAreaSettingItem(UIEnv.getRsb().getString("devicedescription"),
|
||||||
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
|
deviceDescriptionSet = deviceDescription.getTextArea();
|
||||||
|
settings.getView().add(deviceDescription);
|
||||||
|
|
||||||
// DNS服务器设置
|
// DNS服务器设置
|
||||||
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
|
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
|
||||||
CONST.itemwidth, CONST.settingheight * 5);
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
dnsAreaSet = dns.getTextArea();
|
dnsAreaSet = dns.getTextArea();
|
||||||
settings.getView().add(dns);
|
settings.getView().add(dns);
|
||||||
|
|
||||||
|
// 额外路由设置
|
||||||
|
TextAreaSettingItem extraRoutes = new TextAreaSettingItem(UIEnv.getRsb().getString("extraroutes"),
|
||||||
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
|
extraRoutesSet = extraRoutes.getTextArea();
|
||||||
|
settings.getView().add(extraRoutes);
|
||||||
|
|
||||||
// ASN设置
|
// ASN设置
|
||||||
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
|
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
|
||||||
CONST.itemwidth, CONST.settingheight);
|
CONST.itemwidth, CONST.settingheight);
|
||||||
@@ -888,8 +915,10 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
|
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
|
||||||
CONST.itemwidth, CONST.settingheight);
|
CONST.itemwidth, CONST.settingheight);
|
||||||
comboPerformance = performanceStrategy.getComboBox();
|
comboPerformance = performanceStrategy.getComboBox();
|
||||||
comboPerformance.addItem("BBR");
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.SINGLE_CORE));
|
||||||
comboPerformance.addItem("Vegas2");
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_FILL));
|
||||||
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_SCATTER));
|
||||||
|
comboPerformance.setSelectedIndex(1);
|
||||||
settings.getView().add(performanceStrategy);
|
settings.getView().add(performanceStrategy);
|
||||||
|
|
||||||
|
|
||||||
@@ -1004,7 +1033,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private void saveConfig() {
|
private void saveConfig() {
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
config = new KLALBConfig();
|
config = new KLALBConfig();
|
||||||
config.add(new KLALBControllerConfigItem("KLALBController"));
|
config.add(new KLALBControllerConfigItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (KLALBConfigItem item : config) {
|
for (KLALBConfigItem item : config) {
|
||||||
@@ -1016,6 +1045,26 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
kck.setNogui(nogui.isSelected());
|
kck.setNogui(nogui.isSelected());
|
||||||
|
|
||||||
|
// 保存设备名称
|
||||||
|
String dnametext = deviceNameSet.getText().trim();
|
||||||
|
if (dnametext.equals("")) {
|
||||||
|
kck.setDeviceName(null);
|
||||||
|
} else {
|
||||||
|
kck.setDeviceName(dnametext);
|
||||||
|
}
|
||||||
|
if (kcontroller != null && kcontroller.getIpv6Router() != null) {
|
||||||
|
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存设备描述
|
||||||
|
String ddesctext = deviceDescriptionSet.getText().trim();
|
||||||
|
if (ddesctext.equals("")) {
|
||||||
|
kck.setDeviceDescription(null);
|
||||||
|
} else {
|
||||||
|
kck.setDeviceDescription(ddesctext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 保存IPv6地址
|
// 保存IPv6地址
|
||||||
String ttext = addressFieldSet.getText().trim();
|
String ttext = addressFieldSet.getText().trim();
|
||||||
if (ttext.equals("")) {
|
if (ttext.equals("")) {
|
||||||
@@ -1052,6 +1101,24 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
kck.setDNS(iaddr);
|
kck.setDNS(iaddr);
|
||||||
|
|
||||||
|
// 保存额外路由列表
|
||||||
|
String[] spltEr = extraRoutesSet.getText().split("\n");
|
||||||
|
List<String> eroutes = new ArrayList<>();
|
||||||
|
for (String str : spltEr) {
|
||||||
|
str = str.trim();
|
||||||
|
if (!str.isEmpty()) {
|
||||||
|
try {
|
||||||
|
eroutes.add(KLALBUtils.parseCidr(str));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildextraroutes"),
|
||||||
|
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kck.setExtraRoutes(eroutes);
|
||||||
|
|
||||||
// 保存ASN
|
// 保存ASN
|
||||||
String asntext = asnFieldSet.getText();
|
String asntext = asnFieldSet.getText();
|
||||||
if (asntext.equals("")) {
|
if (asntext.equals("")) {
|
||||||
@@ -1067,8 +1134,8 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String tunNameText=tunDeviceName.getText();
|
String tunNameText=tunDeviceName.getText().trim();
|
||||||
if(tunNameText.equals("")) {
|
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
|
||||||
kck.setTUNName(null);
|
kck.setTUNName(null);
|
||||||
}else {
|
}else {
|
||||||
kck.setTUNName(tunNameText);
|
kck.setTUNName(tunNameText);
|
||||||
@@ -1080,7 +1147,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
kck.setTCPListen(null);
|
kck.setTCPListen(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
kck.setTCPListen(new MultipurposeSocketAddress(tcptext));
|
kck.setTCPListen(new MultiProtocolSocketAddress(tcptext));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
|
||||||
@@ -1095,7 +1162,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
kck.setUDPListen(null);
|
kck.setUDPListen(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
kck.setUDPListen(new MultipurposeSocketAddress(udptext));
|
kck.setUDPListen(new MultiProtocolSocketAddress(udptext,"udp"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
|
||||||
@@ -1106,13 +1173,13 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
// 保存开放线路表
|
// 保存开放线路表
|
||||||
String[] splt1 = openLineTabelSet.getText().split("\n");
|
String[] splt1 = openLineTabelSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr1 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr1 = new ArrayList<>();
|
||||||
boolean show1 = true;
|
boolean show1 = true;
|
||||||
for (String str : splt1) {
|
for (String str : splt1) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr1.add(new MultipurposeSocketAddress(str));
|
iaddr1.add(new MultiProtocolSocketAddress(str));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show1) {
|
if (show1) {
|
||||||
@@ -1127,13 +1194,13 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
// 保存自动连接线路表
|
// 保存自动连接线路表
|
||||||
String[] splt11 = connectLineTabelSet.getText().split("\n");
|
String[] splt11 = connectLineTabelSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr11 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr11 = new ArrayList<>();
|
||||||
boolean show11 = true;
|
boolean show11 = true;
|
||||||
for (String str : splt11) {
|
for (String str : splt11) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr11.add(new MultipurposeSocketAddress(str));
|
iaddr11.add(new MultiProtocolSocketAddress(str));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show11) {
|
if (show11) {
|
||||||
@@ -1148,13 +1215,13 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
// 保存NTP服务器表
|
// 保存NTP服务器表
|
||||||
String[] splt111 = ntpServerSet.getText().split("\n");
|
String[] splt111 = ntpServerSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr111 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr111 = new ArrayList<>();
|
||||||
boolean show111 = true;
|
boolean show111 = true;
|
||||||
for (String str : splt111) {
|
for (String str : splt111) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr111.add(new MultipurposeSocketAddress(str));
|
iaddr111.add(new MultiProtocolSocketAddress(str,"ntp"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show111) {
|
if (show111) {
|
||||||
@@ -1180,6 +1247,11 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected());
|
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected());
|
||||||
|
|
||||||
|
PerformanceStrategyItem psi=((PerformanceStrategyItem)comboPerformance.getSelectedItem());
|
||||||
|
if(psi!=null) {
|
||||||
|
kck.setPerformanceStrategy(psi.getStrategy().toString());
|
||||||
|
}
|
||||||
|
|
||||||
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
|
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
|
||||||
|
|
||||||
kck.setBurstLimit(1.0+burstLimit.getSlider().getValue()/1000.0);
|
kck.setBurstLimit(1.0+burstLimit.getSlider().getValue()/1000.0);
|
||||||
@@ -1213,58 +1285,60 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
tsk = new TimerTask() {
|
tsk = new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Object[] kll = kc.getLines().toArray();
|
if (isVisible()) {
|
||||||
|
Object[] kll = kc.getLines().toArray();
|
||||||
|
|
||||||
// 遍历所有线路,更新或添加TPanel2
|
// 遍历所有线路,更新或添加TPanel2
|
||||||
for (Object obj : kll) {
|
for (Object obj : kll) {
|
||||||
IPv6NetworkLink link = (IPv6NetworkLink) obj;
|
IPv6NetworkLink link = (IPv6NetworkLink) obj;
|
||||||
if (!(link instanceof KLALBRemoteLink)) {
|
if (!(link instanceof KLALBRemoteLink)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
KLALBRemoteLink ent = (KLALBRemoteLink) link;
|
||||||
|
|
||||||
|
Component[] count = ysp.getView().getComponents();
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
// 查找是否已有对应线路的面板
|
||||||
|
for (Component tp : count) {
|
||||||
|
if (tp instanceof TPanel2) {
|
||||||
|
if (((TPanel2) tp).getTunnel().equals(ent)) {
|
||||||
|
tp.setVisible(showoffline.isSelected() || (
|
||||||
|
ent.getState() != LinkStatus.DOWN));
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到,创建新面板
|
||||||
|
if (!found) {
|
||||||
|
TPanel2 tp = new TPanel2(ent, kc);
|
||||||
|
tp.setVisible(showoffline.isSelected() || (
|
||||||
|
ent.getState() != LinkStatus.DOWN));
|
||||||
|
ysp.getView().add(tp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KLALBRemoteLink ent = (KLALBRemoteLink) link;
|
|
||||||
|
|
||||||
|
// 更新所有面板并移除不存在的线路
|
||||||
Component[] count = ysp.getView().getComponents();
|
Component[] count = ysp.getView().getComponents();
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
// 查找是否已有对应线路的面板
|
|
||||||
for (Component tp : count) {
|
for (Component tp : count) {
|
||||||
if (tp instanceof TPanel2) {
|
if (tp instanceof TPanel2) {
|
||||||
if (((TPanel2) tp).getTunnel().equals(ent)) {
|
if (kc.getLines().contains(((TPanel2) tp).getTunnel())) {
|
||||||
tp.setVisible(showoffline.isSelected() || (
|
try {
|
||||||
ent.getState() != LinkStatus.DOWN));
|
if (tp.isVisible())
|
||||||
found = true;
|
((TPanel2) tp).updateTraffic();
|
||||||
break;
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ysp.getView().remove(tp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有找到,创建新面板
|
ysp.updateScrool();
|
||||||
if (!found) {
|
|
||||||
TPanel2 tp = new TPanel2(ent, kc);
|
|
||||||
tp.setVisible(showoffline.isSelected() || (
|
|
||||||
ent.getState()!= LinkStatus.DOWN));
|
|
||||||
ysp.getView().add(tp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新所有面板并移除不存在的线路
|
|
||||||
Component[] count = ysp.getView().getComponents();
|
|
||||||
for (Component tp : count) {
|
|
||||||
if (tp instanceof TPanel2) {
|
|
||||||
if (kc.getLines().contains(((TPanel2) tp).getTunnel())) {
|
|
||||||
try {
|
|
||||||
if (tp.isVisible())
|
|
||||||
((TPanel2) tp).updateTraffic();
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ysp.getView().remove(tp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ysp.updateScrool();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
t.scheduleAtFixedRate(tsk, 1000, 1000);
|
t.scheduleAtFixedRate(tsk, 1000, 1000);
|
||||||
@@ -1440,6 +1514,14 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
nogui.setSelected( kck.isNogui());
|
nogui.setSelected( kck.isNogui());
|
||||||
|
|
||||||
|
// 加载设备名称
|
||||||
|
String dname = kck.getDeviceName();
|
||||||
|
deviceNameSet.setText(dname != null ? dname : "");
|
||||||
|
|
||||||
|
// 加载设备描述
|
||||||
|
String ddesc = kck.getDeviceDescription();
|
||||||
|
deviceDescriptionSet.setText(ddesc != null ? ddesc : "");
|
||||||
|
|
||||||
// 加载IPv6地址
|
// 加载IPv6地址
|
||||||
String vaddr = kck.getVirtualAddress();
|
String vaddr = kck.getVirtualAddress();
|
||||||
addressFieldSet.setText(vaddr != null ? vaddr : "");
|
addressFieldSet.setText(vaddr != null ? vaddr : "");
|
||||||
@@ -1448,6 +1530,10 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
List<InetAddress> dns = kck.getDNS();
|
List<InetAddress> dns = kck.getDNS();
|
||||||
dnsAreaSet.setText(listToStr(dns));
|
dnsAreaSet.setText(listToStr(dns));
|
||||||
|
|
||||||
|
// 加载额外路由
|
||||||
|
List<String> ers = kck.getExtraRoutes();
|
||||||
|
extraRoutesSet.setText(listToStr2(ers));
|
||||||
|
|
||||||
// 加载ASN
|
// 加载ASN
|
||||||
Long vasn = kck.getVirtualASN();
|
Long vasn = kck.getVirtualASN();
|
||||||
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
||||||
@@ -1456,23 +1542,23 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
tunDeviceName.setText(tunname!=null?tunname:"");
|
tunDeviceName.setText(tunname!=null?tunname:"");
|
||||||
|
|
||||||
// 加载TCP监听
|
// 加载TCP监听
|
||||||
MultipurposeSocketAddress mpat = kck.getTCPListen();
|
MultiProtocolSocketAddress mpat = kck.getTCPListen();
|
||||||
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
|
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
|
||||||
|
|
||||||
// 加载UDP监听
|
// 加载UDP监听
|
||||||
MultipurposeSocketAddress mpau = kck.getUDPListen();
|
MultiProtocolSocketAddress mpau = kck.getUDPListen();
|
||||||
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
|
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
|
||||||
|
|
||||||
// 加载开放线路表
|
// 加载开放线路表
|
||||||
List<MultipurposeSocketAddress> linet = kck.getLineTable();
|
List<MultiProtocolSocketAddress> linet = kck.getLineTable();
|
||||||
openLineTabelSet.setText(listToStr2(linet));
|
openLineTabelSet.setText(listToStr2(linet));
|
||||||
|
|
||||||
// 加载自动连接线路表
|
// 加载自动连接线路表
|
||||||
List<MultipurposeSocketAddress> clinet = kck.getConnectLineTable();
|
List<MultiProtocolSocketAddress> clinet = kck.getConnectLineTable();
|
||||||
connectLineTabelSet.setText(listToStr2(clinet));
|
connectLineTabelSet.setText(listToStr2(clinet));
|
||||||
|
|
||||||
// 加载NTP服务器表
|
// 加载NTP服务器表
|
||||||
List<MultipurposeSocketAddress> ntps = kck.getNtpServerTable();
|
List<MultiProtocolSocketAddress> ntps = kck.getNtpServerTable();
|
||||||
ntpServerSet.setText(listToStr2(ntps));
|
ntpServerSet.setText(listToStr2(ntps));
|
||||||
|
|
||||||
// 加载网络接口排除列表
|
// 加载网络接口排除列表
|
||||||
@@ -1497,6 +1583,17 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast());
|
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast());
|
||||||
|
|
||||||
|
String stategy= kck.getPerformanceStrategy();
|
||||||
|
PerformanceStrategy pfs=PerformanceStrategy.fromDescription(stategy);
|
||||||
|
if(pfs!=null) {
|
||||||
|
for (int i=0;i<comboPerformance.getItemCount();i++){
|
||||||
|
if(((PerformanceStrategyItem)comboPerformance.getModel().getElementAt(i)).getStrategy().equals(pfs)) {
|
||||||
|
comboPerformance.setSelectedIndex(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String con=kck.getCongestionAlgorithm();
|
String con=kck.getCongestionAlgorithm();
|
||||||
if(con==null) {
|
if(con==null) {
|
||||||
congestions.getComboBox().setSelectedItem("BBR");
|
congestions.getComboBox().setSelectedItem("BBR");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import javax.swing.border.LineBorder;
|
|||||||
|
|
||||||
import org.jfree.chart.plot.dial.DialTextAnnotation;
|
import org.jfree.chart.plot.dial.DialTextAnnotation;
|
||||||
import org.jfree.data.general.DefaultValueDataset;
|
import org.jfree.data.general.DefaultValueDataset;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.klalb.CONST;
|
import org.kne.cloud.network.klalb.CONST;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
||||||
@@ -143,9 +143,9 @@ public class KperfGUI extends XFrame{
|
|||||||
if(kperf==null) {
|
if(kperf==null) {
|
||||||
try {
|
try {
|
||||||
String tar=targetAddressField.getText();
|
String tar=targetAddressField.getText();
|
||||||
MultipurposeSocketAddress msa=new MultipurposeSocketAddress(tar);
|
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(tar);
|
||||||
String sour=sourceAddressField.getText();
|
String sour=sourceAddressField.getText();
|
||||||
MultipurposeSocketAddress msas=new MultipurposeSocketAddress(sour);
|
MultiProtocolSocketAddress msas=new MultiProtocolSocketAddress(sour);
|
||||||
kperf=new Kperf(msa,msas);
|
kperf=new Kperf(msa,msas);
|
||||||
kperf.setReports(reports);
|
kperf.setReports(reports);
|
||||||
kperf.startPerfing();
|
kperf.startPerfing();
|
||||||
@@ -650,10 +650,10 @@ public class KperfGUI extends XFrame{
|
|||||||
startButton.setText(UIEnv.getRsb().getString("stoptest"));
|
startButton.setText(UIEnv.getRsb().getString("stoptest"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void setTarget(MultipurposeSocketAddress target) {
|
public void setTarget(MultiProtocolSocketAddress target) {
|
||||||
targetAddressField.setText(target.toString());
|
targetAddressField.setText(target.toString());
|
||||||
}
|
}
|
||||||
public void setTarget(MultipurposeSocketAddress target,MultipurposeSocketAddress source) {
|
public void setTarget(MultiProtocolSocketAddress target, MultiProtocolSocketAddress source) {
|
||||||
targetAddressField.setText(target.toString());
|
targetAddressField.setText(target.toString());
|
||||||
sourceAddressField.setText(source.toString());
|
sourceAddressField.setText(source.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,25 @@
|
|||||||
package org.kne.cloud.network.klalb.ui;
|
package org.kne.cloud.network.klalb.ui;
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.FontMetrics;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.Rectangle;
|
|
||||||
import java.awt.Shape;
|
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.awt.geom.Dimension2D;
|
|
||||||
import java.awt.geom.GeneralPath;
|
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
import java.awt.image.ImageObserver;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.text.AttributedCharacterIterator;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.border.LineBorder;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
||||||
|
|
||||||
public class NetworkGraphPanel extends GraphPanel {
|
public class NetworkGraphPanel extends GraphPanel {
|
||||||
@@ -70,7 +47,7 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
|
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
|
||||||
super(getText(address), color, x, y, ismarked);
|
super(getNodeText(address), color, x, y, ismarked);
|
||||||
this.address=address;
|
this.address=address;
|
||||||
initign();
|
initign();
|
||||||
}
|
}
|
||||||
@@ -105,8 +82,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
KperfGUI kpfg=new KperfGUI();
|
KperfGUI kpfg=new KperfGUI();
|
||||||
kpfg.setVisible(true);
|
kpfg.setVisible(true);
|
||||||
MultipurposeSocketAddress source=new MultipurposeSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
MultiProtocolSocketAddress source=new MultiProtocolSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
||||||
MultipurposeSocketAddress target=new MultipurposeSocketAddress("KLALB_Stream", address.toString(), 4564);
|
MultiProtocolSocketAddress target=new MultiProtocolSocketAddress("KLALB_Stream", address.toString(), 4564);
|
||||||
kpfg.setTarget(target,source);
|
kpfg.setTarget(target,source);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -160,17 +137,33 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
return address2.toCompressedString();
|
return address2.toCompressedString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateLabel() {
|
||||||
|
setText(getNodeText(address));
|
||||||
|
}
|
||||||
|
|
||||||
public IPv6Address getAddress() {
|
public IPv6Address getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAddress(IPv6Address address) {
|
public void setAddress(IPv6Address address) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
super.setText(getText(address));
|
super.setText(getNodeText(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点标签:显示广播得知的设备名称(若有)+IP地址
|
||||||
|
*/
|
||||||
|
private String getNodeText(IPv6Address address) {
|
||||||
|
StringBuilder sb=new StringBuilder();
|
||||||
|
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
|
||||||
|
if(dname!=null)
|
||||||
|
sb.append(dname).append('\n');
|
||||||
|
sb.append(InetGraphNode.getText(address));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private double nsPerPixel=1000L;
|
private double nsPerPixel=1000L;
|
||||||
private class InetGraphEdgeGroup extends GraphEdgeGroup{
|
private class InetGraphEdgeGroup extends GraphEdgeGroup{
|
||||||
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
|
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
|
||||||
@@ -201,6 +194,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
||||||
if(!addr.containsKey(inet6Address)) {
|
if(!addr.containsKey(inet6Address)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
}else {
|
||||||
|
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//System.out.println("------------------------------------");
|
//System.out.println("------------------------------------");
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import java.awt.Image;
|
|||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
@@ -22,7 +21,7 @@ import javax.swing.event.ListSelectionEvent;
|
|||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import org.kne.cloud.klalb.uitool.XDefaultListModel;
|
import org.kne.cloud.klalb.uitool.XDefaultListModel;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||||
@@ -30,7 +29,6 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
|||||||
import javax.swing.JTabbedPane;
|
import javax.swing.JTabbedPane;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -43,7 +41,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
private KLALBController controller;
|
private KLALBController controller;
|
||||||
private Image image;
|
private Image image;
|
||||||
|
|
||||||
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>();
|
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
|
||||||
public KLALBController getController() {
|
public KLALBController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
@@ -69,11 +67,32 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
panel.setLayout(new BorderLayout(0, 0));
|
panel.setLayout(new BorderLayout(0, 0));
|
||||||
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
|
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
|
||||||
|
|
||||||
|
JTextArea overviewArea = new JTextArea();
|
||||||
|
overviewArea.setEditable(false);
|
||||||
|
overviewArea.setLineWrap(true);
|
||||||
|
overviewArea.setWrapStyleWord(true);
|
||||||
|
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
|
||||||
|
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||||
|
StringBuilder sb=new StringBuilder();
|
||||||
|
sb.append(UIEnv.getRsb().getString("ipv6addr")).append(": ").append(address.toCompressedString()).append('\n');
|
||||||
|
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
|
||||||
|
if(dname!=null) {
|
||||||
|
sb.append('\n').append(UIEnv.getRsb().getString("devicename")).append(": ").append(dname).append('\n');
|
||||||
|
}
|
||||||
|
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
|
||||||
|
String ddesc=controller.getConfigItem().getDeviceDescription();
|
||||||
|
if(ddesc!=null&&!ddesc.isEmpty()) {
|
||||||
|
sb.append('\n').append(UIEnv.getRsb().getString("devicedescription")).append(":\n").append(ddesc).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overviewArea.setText(sb.toString());
|
||||||
|
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel panel_1 = new JPanel();
|
JPanel panel_1 = new JPanel();
|
||||||
panel_1.setLayout(new BorderLayout(0, 0));
|
panel_1.setLayout(new BorderLayout(0, 0));
|
||||||
panel_1.add(scrollPane);
|
panel_1.add(scrollPane);
|
||||||
|
|
||||||
JList<MultipurposeSocketAddress> list = new JList<MultipurposeSocketAddress>(listModel);
|
JList<MultiProtocolSocketAddress> list = new JList<MultiProtocolSocketAddress>(listModel);
|
||||||
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
scrollPane.setViewportView(list);
|
scrollPane.setViewportView(list);
|
||||||
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
|
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
|
||||||
@@ -81,7 +100,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||||
btnNewButton.addActionListener(new ActionListener() {
|
btnNewButton.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
controller.addRemoteLines(select);
|
controller.addRemoteLines(select);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,7 +112,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void valueChanged(ListSelectionEvent e) {
|
public void valueChanged(ListSelectionEvent e) {
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
btnNewButton.setEnabled( !select.isEmpty()) ;
|
btnNewButton.setEnabled( !select.isEmpty()) ;
|
||||||
copy.setEnabled(!select.isEmpty());
|
copy.setEnabled(!select.isEmpty());
|
||||||
}
|
}
|
||||||
@@ -103,9 +122,9 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
StringBuilder sb=new StringBuilder();
|
StringBuilder sb=new StringBuilder();
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
for (MultipurposeSocketAddress multipurposeSocketAddress : select) {
|
for (MultiProtocolSocketAddress multiProtocolSocketAddress : select) {
|
||||||
sb.append(multipurposeSocketAddress);
|
sb.append(multiProtocolSocketAddress);
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
|
||||||
@@ -152,8 +171,8 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
try {
|
try {
|
||||||
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{
|
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
for (MultipurposeSocketAddress multipurposeSocketAddress : result) {
|
for (MultiProtocolSocketAddress multiProtocolSocketAddress : result) {
|
||||||
listModel.addElement(multipurposeSocketAddress);
|
listModel.addElement(multiProtocolSocketAddress);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.kne.cloud.network.klalb.ui;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
|
||||||
|
public class PerformanceStrategyItem {
|
||||||
|
private PerformanceStrategy strategy;
|
||||||
|
|
||||||
|
public PerformanceStrategyItem(PerformanceStrategy strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceStrategy getStrategy() {
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return UIEnv.getRsb().getString(strategy.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import java.awt.Dimension;
|
|||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.border.LineBorder;
|
import javax.swing.border.LineBorder;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||||
import org.kne.cloud.network.klalb.CONST;
|
import org.kne.cloud.network.klalb.CONST;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
@@ -219,7 +219,7 @@ public class TPanel2 extends JPanel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
MultipurposeSocketAddress mtar=tunnel.getSocketAddress();
|
MultiProtocolSocketAddress mtar=tunnel.getSocketAddress();
|
||||||
if(mtar!=null) {
|
if(mtar!=null) {
|
||||||
kc.removeRemoteLines(mtar);
|
kc.removeRemoteLines(mtar);
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
|||||||
case KLTPPacket.KLTP_TYPE_DATA:
|
case KLTPPacket.KLTP_TYPE_DATA:
|
||||||
//ackSequenceBatcher.putMessage(kseq);
|
//ackSequenceBatcher.putMessage(kseq);
|
||||||
recvMap.put(kltp.getSequence(), kltp);
|
recvMap.put(kltp.getSequence(), kltp);
|
||||||
controller.getIpv6Router().runPacketSendTask(()->{
|
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||||
pack.setCE(u.isCE());
|
pack.setCE(u.isCE());
|
||||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||||
@@ -267,7 +267,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
|||||||
case KLTPPacket.KLTP_TYPE_DATAFIN:
|
case KLTPPacket.KLTP_TYPE_DATAFIN:
|
||||||
//ackSequenceBatcher.putMessage(kseq2);
|
//ackSequenceBatcher.putMessage(kseq2);
|
||||||
recvMap.put(kltp.getSequence(), kltp);
|
recvMap.put(kltp.getSequence(), kltp);
|
||||||
controller.getIpv6Router().runPacketSendTask(()->{
|
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||||
pack.setCE(u.isCE());
|
pack.setCE(u.isCE());
|
||||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
package org.kne.cloud.network.minecraft;
|
package org.kne.cloud.network.minecraft;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||||
import org.kne.cloud.network.scanner.PortRange;
|
import org.kne.cloud.network.scanner.PortRange;
|
||||||
import org.kne.cloud.network.scanner.ScanRange;
|
import org.kne.cloud.network.scanner.ScanRange;
|
||||||
import org.kne.cloud.network.scanner.TCPNetworkScanner;
|
import org.kne.cloud.network.scanner.TCPNetworkScanner;
|
||||||
|
|
||||||
public class MinecraftScanner extends TCPNetworkScanner{
|
public class MinecraftScanner extends TCPNetworkScanner{
|
||||||
private BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
private BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
||||||
|
|
||||||
public BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
public BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
||||||
return minecraftConsumer;
|
return minecraftConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMinecraftConsumer(BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
public void setMinecraftConsumer(BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
||||||
this.minecraftConsumer = minecraftConsumer;
|
this.minecraftConsumer = minecraftConsumer;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import javax.naming.directory.Attributes;
|
|||||||
import javax.naming.directory.DirContext;
|
import javax.naming.directory.DirContext;
|
||||||
import javax.naming.directory.InitialDirContext;
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class MinecraftServerAddress extends MultipurposeSocketAddress
|
public class MinecraftServerAddress extends MultiProtocolSocketAddress
|
||||||
{
|
{
|
||||||
|
|
||||||
private String originAddr;
|
private String originAddr;
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ package org.kne.cloud.network.minecraft;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectOutput;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.StringReader;
|
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@@ -19,13 +13,11 @@ import java.util.Base64;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.google.gson.stream.JsonToken;
|
|
||||||
|
|
||||||
public class MinecraftServerPinger implements Serializable{
|
public class MinecraftServerPinger implements Serializable{
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +26,7 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private transient Socket socket;
|
private transient Socket socket;
|
||||||
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
|
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
|
||||||
private MultipurposeSocketAddress targetaddr;
|
private MultiProtocolSocketAddress targetaddr;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -53,11 +45,11 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
private String icon;
|
private String icon;
|
||||||
private transient BufferedImage image;
|
private transient BufferedImage image;
|
||||||
|
|
||||||
public MinecraftServerPinger(MultipurposeSocketAddress target) throws UnknownHostException, IOException {
|
public MinecraftServerPinger(MultiProtocolSocketAddress target) throws UnknownHostException, IOException {
|
||||||
this(target,target.connectSocket());
|
this(target,target.connectSocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftServerPinger(MultipurposeSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
public MinecraftServerPinger(MultiProtocolSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
||||||
targetaddr=target;
|
targetaddr=target;
|
||||||
socket=Asocket;
|
socket=Asocket;
|
||||||
socket.setTcpNoDelay(true);
|
socket.setTcpNoDelay(true);
|
||||||
@@ -302,7 +294,7 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTargetaddr() {
|
public MultiProtocolSocketAddress getTargetaddr() {
|
||||||
return targetaddr;
|
return targetaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.kne.cloud.network.minecraft;
|
|||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||||
import org.kne.cloud.network.scanner.PortRange;
|
import org.kne.cloud.network.scanner.PortRange;
|
||||||
import org.kne.cloud.network.scanner.ScanRange;
|
import org.kne.cloud.network.scanner.ScanRange;
|
||||||
@@ -13,7 +13,7 @@ public class MinecraftServerScanRange extends ScanRange {
|
|||||||
super(addressRange, portRange);
|
super(addressRange, portRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftServerScanRange(MultipurposeSocketAddress begin, MultipurposeSocketAddress end)
|
public MinecraftServerScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end)
|
||||||
throws UnknownHostException {
|
throws UnknownHostException {
|
||||||
super(begin, end);
|
super(begin, end);
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ public class MinecraftServerScanRange extends ScanRange {
|
|||||||
@Override
|
@Override
|
||||||
protected void setMsaString(String range) throws UnknownHostException {
|
protected void setMsaString(String range) throws UnknownHostException {
|
||||||
String[]sp=range.split("~");
|
String[]sp=range.split("~");
|
||||||
MultipurposeSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
MultiProtocolSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
||||||
MultipurposeSocketAddress end;
|
MultiProtocolSocketAddress end;
|
||||||
if(sp.length>1) {
|
if(sp.length>1) {
|
||||||
end=MinecraftServerAddress.findAddress (sp[1]);
|
end=MinecraftServerAddress.findAddress (sp[1]);
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
|
import org.kne.concurrent.LazyEvaluator;
|
||||||
|
|
||||||
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
|
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
|
||||||
|
|
||||||
@@ -16,11 +17,12 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final long TIME_WINDOW=2000000000L;
|
||||||
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
|
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
|
||||||
super();
|
super();
|
||||||
this.clock=clock;
|
this.clock=clock;
|
||||||
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, 5000000000L);
|
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, TIME_WINDOW);
|
||||||
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, 5000000000L);
|
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, TIME_WINDOW);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +88,13 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> outSpeedEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculateBandwidth(timewindow);});
|
||||||
public long getOutSpeed() {
|
public long getOutSpeed() {
|
||||||
return uploadBandwidth.calculateBandwidth(timewindow);
|
return outSpeedEvaluator.get();
|
||||||
}
|
}
|
||||||
|
private LazyEvaluator<Long> inSpeedEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculateBandwidth(timewindow);});
|
||||||
public long getInSpeed() {
|
public long getInSpeed() {
|
||||||
return downloadBandwidth.calculateBandwidth(timewindow);
|
return inSpeedEvaluator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -117,13 +120,15 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> outPPSEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculatePacketRate(timewindow);});
|
||||||
public long getOutPPS() {
|
public long getOutPPS() {
|
||||||
return uploadBandwidth.calculatePacketRate(timewindow);
|
return outPPSEvaluator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> inPPSEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculatePacketRate(timewindow);});
|
||||||
|
|
||||||
public long getInPPS() {
|
public long getInPPS() {
|
||||||
return downloadBandwidth.calculatePacketRate(timewindow);
|
return inPPSEvaluator.get();
|
||||||
}
|
}
|
||||||
private long inSpeed;
|
private long inSpeed;
|
||||||
private long outSpeed;
|
private long outSpeed;
|
||||||
@@ -159,9 +164,18 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
public void update() {
|
public void update() {
|
||||||
upSampler.update();
|
upSampler.update();
|
||||||
downSampler.update();
|
downSampler.update();
|
||||||
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)upSampler.getPacketCountSinceLastSnapshot() ,(int)upSampler.getByteCountSinceLastSnapshot());
|
long uppacketcount=upSampler.getPacketCountSinceLastSnapshot();
|
||||||
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)downSampler.getPacketCountSinceLastSnapshot() ,(int) downSampler.getByteCountSinceLastSnapshot());
|
if(uppacketcount>0) {
|
||||||
|
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) uppacketcount, (int) upSampler.getByteCountSinceLastSnapshot());
|
||||||
|
outSpeedEvaluator.markDirty();
|
||||||
|
outPPSEvaluator.markDirty();
|
||||||
|
}
|
||||||
|
long downpacketcount=downSampler.getPacketCountSinceLastSnapshot();
|
||||||
|
if(downpacketcount>0) {
|
||||||
|
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) downpacketcount, (int) downSampler.getByteCountSinceLastSnapshot());
|
||||||
|
inSpeedEvaluator.markDirty();
|
||||||
|
inPPSEvaluator.markDirty();
|
||||||
|
}
|
||||||
long time=System.nanoTime();
|
long time=System.nanoTime();
|
||||||
if(time-stime>timewindowmax) {
|
if(time-stime>timewindowmax) {
|
||||||
stime=time;
|
stime=time;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||||
import org.kne.math.Long128;
|
import org.kne.math.Long128;
|
||||||
|
|
||||||
@@ -110,13 +110,13 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>>();
|
private ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>>();
|
||||||
|
|
||||||
protected void clearPackets() {
|
protected void clearPackets() {
|
||||||
recvmap.clear();
|
recvmap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void putPacket(NTPv4Packet nv4, MultipurposeSocketAddress inetSocketAddress) {
|
protected void putPacket(NTPv4Packet nv4, MultiProtocolSocketAddress inetSocketAddress) {
|
||||||
checkIP();
|
checkIP();
|
||||||
List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
|
List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
|
||||||
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
|
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
|
||||||
@@ -133,10 +133,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkIP() {
|
private void checkIP() {
|
||||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||||
.hasNext();) {
|
.hasNext();) {
|
||||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||||
.next();
|
.next();
|
||||||
AtomicBoolean ab = new AtomicBoolean(false);
|
AtomicBoolean ab = new AtomicBoolean(false);
|
||||||
ios.forEach((x) -> {
|
ios.forEach((x) -> {
|
||||||
@@ -153,7 +153,7 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class PeerInfo implements Comparable<PeerInfo> {
|
private class PeerInfo implements Comparable<PeerInfo> {
|
||||||
private MultipurposeSocketAddress address;
|
private MultiProtocolSocketAddress address;
|
||||||
private int leapIndicator = 3;
|
private int leapIndicator = 3;
|
||||||
private int stratum = 16;
|
private int stratum = 16;
|
||||||
private int referenceIdentifier;
|
private int referenceIdentifier;
|
||||||
@@ -265,10 +265,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private List<PeerInfo> mergeResponses() {
|
private List<PeerInfo> mergeResponses() {
|
||||||
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
|
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
|
||||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||||
.hasNext();) {
|
.hasNext();) {
|
||||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
List<NTPv4Packet> newv = entry.getValue();
|
List<NTPv4Packet> newv = entry.getValue();
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.kne.cloud.network.ntp;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.UDPSocketType;
|
||||||
|
|
||||||
|
public class NTPSocketType extends UDPSocketType {
|
||||||
|
private static final NTPSocketType INSTANCE = new NTPSocketType();
|
||||||
|
|
||||||
|
private NTPSocketType() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NTPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefaultPort() {
|
||||||
|
return 123;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.io.KNEChannels;
|
import org.kne.io.KNEChannels;
|
||||||
import org.kne.math.Long128;
|
import org.kne.math.Long128;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
private static final long EPHEMERAL_TIMEOUT = 60000000000L;
|
private static final long EPHEMERAL_TIMEOUT = 60000000000L;
|
||||||
|
|
||||||
public static class NTPPeer {
|
public static class NTPPeer {
|
||||||
private MultipurposeSocketAddress address;
|
private MultiProtocolSocketAddress address;
|
||||||
private boolean isEphemeral;// ephemeral
|
private boolean isEphemeral;// ephemeral
|
||||||
private volatile long ephemeralUpdateTime = System.nanoTime();
|
private volatile long ephemeralUpdateTime = System.nanoTime();
|
||||||
private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
|
private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
|
||||||
@@ -70,11 +71,11 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
this.pollInterval = pollInterval;
|
this.pollInterval = pollInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NTPPeer(MultipurposeSocketAddress address, int requestMode) {
|
public NTPPeer(MultiProtocolSocketAddress address, int requestMode) {
|
||||||
this(address, requestMode, false);
|
this(address, requestMode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected NTPPeer(MultipurposeSocketAddress address, int requestMode, boolean isEphemeral) {
|
protected NTPPeer(MultiProtocolSocketAddress address, int requestMode, boolean isEphemeral) {
|
||||||
super();
|
super();
|
||||||
checkRequestMode(requestMode);
|
checkRequestMode(requestMode);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
@@ -90,10 +91,10 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public NTPPeer(String hostport, int requestMode) {
|
public NTPPeer(String hostport, int requestMode) {
|
||||||
this(new MultipurposeSocketAddress(hostport), requestMode);
|
this(new MultiProtocolSocketAddress(hostport), requestMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getAddress() {
|
public MultiProtocolSocketAddress getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,17 +154,28 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private NTPContext context;
|
private NTPContext context;
|
||||||
private DatagramSocket dgs;
|
private DatagramSocket dgs;
|
||||||
private MultipurposeSocketAddress bind;
|
private MultiProtocolSocketAddress bind;
|
||||||
|
|
||||||
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
||||||
this(context, new MultipurposeSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
this(context, new MultiProtocolSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NTPv4Protocol(NTPContext context, MultipurposeSocketAddress bind) throws UnknownHostException, IOException {
|
public NTPv4Protocol(NTPContext context, MultiProtocolSocketAddress bind) throws UnknownHostException, IOException {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
dgs = bind.listenDatagramSocket();
|
dgs = bind.listenDatagramSocket();
|
||||||
context.registerIO(this);
|
context.registerIO(this);
|
||||||
|
Thread t4= ThreadTool.makeVThread("NTPv4 Packet Cleaner",()->{
|
||||||
|
while(isClosed()){
|
||||||
|
removeTimeoutTimestamp();
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t4.start();
|
||||||
Thread tr = new Thread(recv);
|
Thread tr = new Thread(recv);
|
||||||
tr.setName("NTPv4 Receive Thread");
|
tr.setName("NTPv4 Receive Thread");
|
||||||
tr.start();
|
tr.start();
|
||||||
@@ -219,7 +231,6 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
|
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
|
||||||
checkLock.lock();
|
checkLock.lock();
|
||||||
try {
|
try {
|
||||||
removeTimeoutTimestamp();
|
|
||||||
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
||||||
} finally {
|
} finally {
|
||||||
checkLock.unlock();
|
checkLock.unlock();
|
||||||
@@ -227,13 +238,18 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeTimeoutTimestamp() {
|
private void removeTimeoutTimestamp() {
|
||||||
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext();) {
|
checkLock.lock();
|
||||||
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
try {
|
||||||
if (bigInteger.checkTimeout()) {
|
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext(); ) {
|
||||||
if (showPacket)
|
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
||||||
System.out.println("timeout:" + bigInteger.timeStamp);
|
if (bigInteger.checkTimeout()) {
|
||||||
iterator.remove();
|
if (showPacket)
|
||||||
|
System.out.println("timeout:" + bigInteger.timeStamp);
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}finally{
|
||||||
|
checkLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +282,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTPPeer findPeer(MultipurposeSocketAddress addr) {
|
NTPPeer findPeer(MultiProtocolSocketAddress addr) {
|
||||||
Object[] objs = peers.toArray();
|
Object[] objs = peers.toArray();
|
||||||
for (int i = 0; i < objs.length; i++) {
|
for (int i = 0; i < objs.length; i++) {
|
||||||
NTPPeer np = (NTPPeer) objs[i];
|
NTPPeer np = (NTPPeer) objs[i];
|
||||||
@@ -291,7 +307,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
while (!dgs.isClosed()) {
|
while (!dgs.isClosed()) {
|
||||||
AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
|
AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
|
||||||
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
||||||
MultipurposeSocketAddress mpsafrom = new MultipurposeSocketAddress(bind.getType(), isa.get());
|
MultiProtocolSocketAddress mpsafrom = new MultiProtocolSocketAddress(bind.getProtocol(), isa.get());
|
||||||
switch (nv4.getMode()) {
|
switch (nv4.getMode()) {
|
||||||
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
|
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
|
||||||
NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
|
NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
|
||||||
@@ -393,7 +409,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
HighAccuracyClock hac = new HighAccuracyClock();
|
HighAccuracyClock hac = new HighAccuracyClock();
|
||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
|
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
|
||||||
|
|
||||||
public class TestNTP1 {
|
public class TestNTP1 {
|
||||||
public static void main(String[] args) throws UnknownHostException, IOException {
|
public static void main(String[] args) throws UnknownHostException, IOException {
|
||||||
@@ -13,7 +12,7 @@ public class TestNTP1 {
|
|||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
context.syncToSystem();
|
context.syncToSystem();
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||||
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||||
|
|
||||||
public class TestNTP2 {
|
public class TestNTP2 {
|
||||||
@@ -13,7 +13,7 @@ public class TestNTP2 {
|
|||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
context.syncToSystem();
|
context.syncToSystem();
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.AdjustedNanoClock;
|
import org.kne.cloud.clock.AdjustedNanoClock;
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
import org.kne.cloud.network.SpeedLimiter;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.klalb.KLALBPacket;
|
import org.kne.cloud.network.klalb.KLALBPacket;
|
||||||
@@ -32,8 +32,8 @@ public class Kperf implements Runnable{
|
|||||||
|
|
||||||
private static final boolean showpacket = false;
|
private static final boolean showpacket = false;
|
||||||
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
|
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
|
||||||
private MultipurposeSocketAddress targetAddress;
|
private MultiProtocolSocketAddress targetAddress;
|
||||||
private MultipurposeSocketAddress bindAddress;
|
private MultiProtocolSocketAddress bindAddress;
|
||||||
private volatile boolean connected=false;
|
private volatile boolean connected=false;
|
||||||
private volatile KLALBPacketLink link;
|
private volatile KLALBPacketLink link;
|
||||||
|
|
||||||
@@ -43,13 +43,13 @@ public class Kperf implements Runnable{
|
|||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
private volatile ThreadParker tlock=new ThreadParker();
|
private volatile ThreadParker tlock=new ThreadParker();
|
||||||
public Kperf(MultipurposeSocketAddress targetAddress,MultipurposeSocketAddress bindAddress) {
|
public Kperf(MultiProtocolSocketAddress targetAddress, MultiProtocolSocketAddress bindAddress) {
|
||||||
Objects.requireNonNull(targetAddress);
|
Objects.requireNonNull(targetAddress);
|
||||||
this.targetAddress=targetAddress;
|
this.targetAddress=targetAddress;
|
||||||
this.bindAddress=bindAddress;
|
this.bindAddress=bindAddress;
|
||||||
}
|
}
|
||||||
public Kperf(MultipurposeSocketAddress targetAddress) {
|
public Kperf(MultiProtocolSocketAddress targetAddress) {
|
||||||
this(targetAddress,new MultipurposeSocketAddress("[::0]:0"));
|
this(targetAddress,new MultiProtocolSocketAddress("[::0]:0"));
|
||||||
}
|
}
|
||||||
public Kperf(KLALBPacketLink link) {
|
public Kperf(KLALBPacketLink link) {
|
||||||
Objects.requireNonNull(link);
|
Objects.requireNonNull(link);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SocketChannelListener;
|
import org.kne.cloud.network.SocketChannelListener;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
@@ -58,7 +58,7 @@ for (int i = 0; i < nodes; i++) {
|
|||||||
for (int j = 0; j < 1; j++) {
|
for (int j = 0; j < 1; j++) {
|
||||||
int ind=r.nextInt(size);
|
int ind=r.nextInt(size);
|
||||||
NodeEntry ne=nodeList.get(ind);
|
NodeEntry ne=nodeList.get(ind);
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||||
kct.addRemoteLines(mpsa);
|
kct.addRemoteLines(mpsa);
|
||||||
System.out.println("connect:" +ne.getKc().getSelf().getAddress());
|
System.out.println("connect:" +ne.getKc().getSelf().getAddress());
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ for (int i = 0; i < nodes; i++) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
NodeEntry ne=nodeList.get(0);
|
NodeEntry ne=nodeList.get(0);
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||||
klalbController.addRemoteLines(mpsa);
|
klalbController.addRemoteLines(mpsa);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.io.Serializable;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class ScanRange implements Serializable {
|
public class ScanRange implements Serializable {
|
||||||
public ScanRange(String range) throws UnknownHostException {
|
public ScanRange(String range) throws UnknownHostException {
|
||||||
@@ -12,21 +12,21 @@ public class ScanRange implements Serializable {
|
|||||||
}
|
}
|
||||||
protected void setMsaString(String range) throws UnknownHostException {
|
protected void setMsaString(String range) throws UnknownHostException {
|
||||||
String[]sp=range.split("~");
|
String[]sp=range.split("~");
|
||||||
MultipurposeSocketAddress begin=new MultipurposeSocketAddress(sp[0]);
|
MultiProtocolSocketAddress begin=new MultiProtocolSocketAddress(sp[0]);
|
||||||
MultipurposeSocketAddress end;
|
MultiProtocolSocketAddress end;
|
||||||
if(sp.length>1) {
|
if(sp.length>1) {
|
||||||
end=new MultipurposeSocketAddress(sp[1]);
|
end=new MultiProtocolSocketAddress(sp[1]);
|
||||||
}else {
|
}else {
|
||||||
end=new MultipurposeSocketAddress(sp[0]);
|
end=new MultiProtocolSocketAddress(sp[0]);
|
||||||
}
|
}
|
||||||
setMsa(begin, end);
|
setMsa(begin, end);
|
||||||
}
|
}
|
||||||
public ScanRange(MultipurposeSocketAddress begin,MultipurposeSocketAddress end) throws UnknownHostException {
|
public ScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||||
setMsa(begin,end);
|
setMsa(begin,end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setMsa(MultipurposeSocketAddress begin, MultipurposeSocketAddress end) throws UnknownHostException {
|
protected void setMsa(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||||
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
|
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
|
||||||
this.portRange=new PortRange(begin.getPort(), end.getPort());
|
this.portRange=new PortRange(begin.getPort(), end.getPort());
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ public class ScanRange implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultipurposeSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
return new MultiProtocolSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultiProtocolSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.net.UnknownHostException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@@ -20,11 +19,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
|
|
||||||
public class TCPNetworkScanner implements Runnable {
|
public class TCPNetworkScanner implements Runnable {
|
||||||
private BiConsumer<MultipurposeSocketAddress,Socket> consumer;
|
private BiConsumer<MultiProtocolSocketAddress,Socket> consumer;
|
||||||
private Consumer<Float>processConsumer;
|
private Consumer<Float>processConsumer;
|
||||||
|
|
||||||
public Consumer<Float> getProcessConsumer() {
|
public Consumer<Float> getProcessConsumer() {
|
||||||
@@ -44,11 +43,11 @@ public class TCPNetworkScanner implements Runnable {
|
|||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiConsumer<MultipurposeSocketAddress,Socket> getConsumer() {
|
public BiConsumer<MultiProtocolSocketAddress,Socket> getConsumer() {
|
||||||
return consumer;
|
return consumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConsumer(BiConsumer<MultipurposeSocketAddress,Socket> consumer) {
|
public void setConsumer(BiConsumer<MultiProtocolSocketAddress,Socket> consumer) {
|
||||||
this.consumer = consumer;
|
this.consumer = consumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +127,13 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
|||||||
exc.execute(()->{
|
exc.execute(()->{
|
||||||
|
|
||||||
|
|
||||||
MultipurposeSocketAddress mpsa;
|
MultiProtocolSocketAddress mpsa;
|
||||||
if (currAddress.equals(inetAddressRange.getBegin())) {
|
if (currAddress.equals(inetAddressRange.getBegin())) {
|
||||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
||||||
}else if (currAddress.equals(inetAddressRange.getEnd())) {
|
}else if (currAddress.equals(inetAddressRange.getEnd())) {
|
||||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getEndHost(), curport0);
|
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getEndHost(), curport0);
|
||||||
} else {
|
} else {
|
||||||
mpsa = new MultipurposeSocketAddress(currAddress.getHostAddress(),
|
mpsa = new MultiProtocolSocketAddress(currAddress.getHostAddress(),
|
||||||
curport0);
|
curport0);
|
||||||
}
|
}
|
||||||
Socket s = null;
|
Socket s = null;
|
||||||
@@ -228,7 +227,7 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
|||||||
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
|
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void acceptMultipurposeSocketAddress(MultipurposeSocketAddress scanned, Socket s) {
|
protected void acceptMultipurposeSocketAddress(MultiProtocolSocketAddress scanned, Socket s) {
|
||||||
|
|
||||||
consumer.accept(scanned,s);
|
consumer.accept(scanned,s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,14 @@
|
|||||||
package org.kne.cloud.network.srv6;
|
package org.kne.cloud.network.srv6;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectOutput;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
|
||||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -53,7 +38,7 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
|
|||||||
private static Gson gson;
|
private static Gson gson;
|
||||||
static{
|
static{
|
||||||
GsonBuilder gb=new GsonBuilder();
|
GsonBuilder gb=new GsonBuilder();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
gson=gb.create();
|
gson=gb.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -545,6 +545,7 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
List<IPv6NetworkLink>links=router.getLinkTabel();
|
List<IPv6NetworkLink>links=router.getLinkTabel();
|
||||||
Object[] nls=links.toArray();
|
Object[] nls=links.toArray();
|
||||||
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN());
|
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN());
|
||||||
|
ri.setDeviceName(router.getDeviceName());
|
||||||
for(int i=0;i<nls.length;i++) {
|
for(int i=0;i<nls.length;i++) {
|
||||||
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
|
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
|
||||||
if((!nl.isLoopBack())&&nl.isUp()) {
|
if((!nl.isLoopBack())&&nl.isUp()) {
|
||||||
@@ -595,6 +596,19 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
public Map<IPv6Address, Long> getAddresses() {
|
public Map<IPv6Address, Long> getAddresses() {
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询某地址广播的设备名称(未知返回null)
|
||||||
|
*/
|
||||||
|
public String getDeviceName(IPv6Address address) {
|
||||||
|
RouterInfo ri=netmap.get(address);
|
||||||
|
if(ri!=null) {
|
||||||
|
String dn=ri.getDeviceName();
|
||||||
|
if(dn!=null&&!dn.isEmpty())
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
public Map<IPv6Address, List<LinkDirection>> getPaths() {
|
public Map<IPv6Address, List<LinkDirection>> getPaths() {
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.UUID;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
|
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
|
||||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
||||||
import org.kne.opencl64.Releaser;
|
import org.kne.opencl64.Releaser;
|
||||||
@@ -33,16 +33,16 @@ public class KLALBRoutingProtocolAPIClient {
|
|||||||
|
|
||||||
if ((relate = window.ack(ruid)) != null) {
|
if ((relate = window.ack(ruid)) != null) {
|
||||||
List<?> connects = (List<?>) dataobj.getData();
|
List<?> connects = (List<?>) dataobj.getData();
|
||||||
List<MultipurposeSocketAddress> connectsm = new ArrayList<MultipurposeSocketAddress>(connects.size());
|
List<MultiProtocolSocketAddress> connectsm = new ArrayList<MultiProtocolSocketAddress>(connects.size());
|
||||||
for (Object open : connects) {
|
for (Object open : connects) {
|
||||||
if (open instanceof MultipurposeSocketAddress) {
|
if (open instanceof MultiProtocolSocketAddress) {
|
||||||
connectsm.add((MultipurposeSocketAddress) open);
|
connectsm.add((MultiProtocolSocketAddress) open);
|
||||||
} else {
|
} else {
|
||||||
connectsm.add(new MultipurposeSocketAddress((String) open));
|
connectsm.add(new MultiProtocolSocketAddress((String) open));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
((Consumer<List<MultipurposeSocketAddress>>) relate.getUserCallback()).accept(connectsm);
|
((Consumer<List<MultiProtocolSocketAddress>>) relate.getUserCallback()).accept(connectsm);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ public class KLALBRoutingProtocolAPIClient {
|
|||||||
clr.register(this, releaser);
|
clr.register(this, releaser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestOpenLines(SocketAddress addr, Consumer<List<MultipurposeSocketAddress>> callback)
|
public void requestOpenLines(SocketAddress addr, Consumer<List<MultiProtocolSocketAddress>> callback)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
UUID suid = UUID.randomUUID();
|
UUID suid = UUID.randomUUID();
|
||||||
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
|
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
|
||||||
|
|||||||
@@ -4,13 +4,8 @@ import java.io.IOException;
|
|||||||
import java.lang.ref.Cleaner;
|
import java.lang.ref.Cleaner;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
import org.kne.cloud.network.ThreadTool;
|
|
||||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.opencl64.Releaser;
|
import org.kne.opencl64.Releaser;
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class RouterInfo implements Serializable{
|
|||||||
return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses);
|
return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses);
|
||||||
}
|
}
|
||||||
private List<NeighborInfo> neighborAddresses=new ArrayList<>();
|
private List<NeighborInfo> neighborAddresses=new ArrayList<>();
|
||||||
|
private String deviceName=""; // 设备名称(广播时携带,描述不广播)
|
||||||
private long createTime;
|
private long createTime;
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +91,14 @@ public class RouterInfo implements Serializable{
|
|||||||
return neighborAddresses;
|
return neighborAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
|
public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
|
||||||
this.createTime = createTime;
|
this.createTime = createTime;
|
||||||
this.locator=locator;
|
this.locator=locator;
|
||||||
@@ -106,6 +115,7 @@ public class RouterInfo implements Serializable{
|
|||||||
for( NeighborInfo neighborInfo :neighborAddresses) {
|
for( NeighborInfo neighborInfo :neighborAddresses) {
|
||||||
neighborInfo.writeToStream(out);
|
neighborInfo.writeToStream(out);
|
||||||
}
|
}
|
||||||
|
out.writeUTF(deviceName==null?"":deviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeToChannel(WritableByteChannel dto) throws IOException {
|
protected void writeToChannel(WritableByteChannel dto) throws IOException {
|
||||||
@@ -126,6 +136,7 @@ public class RouterInfo implements Serializable{
|
|||||||
neighborAddresses.add(nif);
|
neighborAddresses.add(nif);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
deviceName=in.readUTF();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import org.kne.cloud.network.ipv6.PostcardEntry;
|
|||||||
import org.kne.cloud.network.ipv6.RouteItem;
|
import org.kne.cloud.network.ipv6.RouteItem;
|
||||||
import org.kne.cloud.network.klalb.KLALBRemoteLink;
|
import org.kne.cloud.network.klalb.KLALBRemoteLink;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
import org.kne.cloud.network.klalb.ui.PerformanceStrategyItem;
|
||||||
import org.kne.concurrent.HighPerformanceExecutor2;
|
import org.kne.concurrent.HighPerformanceExecutor2;
|
||||||
import org.kne.concurrent.TimeoutConcurrentHashMap;
|
import org.kne.concurrent.TimeoutConcurrentHashMap;
|
||||||
import org.pcap4j.packet.IllegalRawDataException;
|
import org.pcap4j.packet.IllegalRawDataException;
|
||||||
@@ -62,6 +64,8 @@ public class SRv6Router {
|
|||||||
|
|
||||||
private static final int IPv6_BITS = 128; // IPv6地址位数
|
private static final int IPv6_BITS = 128; // IPv6地址位数
|
||||||
|
|
||||||
|
private PerformanceStrategy performanceStrategy=PerformanceStrategy.MULTI_FILL;
|
||||||
|
|
||||||
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
|
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
|
||||||
60000000000L);
|
60000000000L);
|
||||||
*/
|
*/
|
||||||
@@ -161,6 +165,14 @@ public class SRv6Router {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public void setPerformanceStrategy(PerformanceStrategy performanceStrategy) {
|
||||||
|
this.performanceStrategy=performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceStrategy getPerformanceStrategy(){
|
||||||
|
return performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
// 链路状态监听器实现
|
// 链路状态监听器实现
|
||||||
private class LinkListener implements IPv6LinkStateListener {
|
private class LinkListener implements IPv6LinkStateListener {
|
||||||
|
|
||||||
@@ -282,6 +294,15 @@ public class SRv6Router {
|
|||||||
|
|
||||||
private IPv6AddressGroup locator; // SRv6定位器
|
private IPv6AddressGroup locator; // SRv6定位器
|
||||||
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
|
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
|
||||||
|
private volatile String deviceName; // 设备名称(随路由信息广播)
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
public IPv6AddressGroup getLocator() {
|
public IPv6AddressGroup getLocator() {
|
||||||
return locator;
|
return locator;
|
||||||
@@ -765,44 +786,44 @@ public class SRv6Router {
|
|||||||
|
|
||||||
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
|
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
|
||||||
|
|
||||||
public static HighPerformanceExecutor2 getDefaultExecutor() {
|
/*public static HighPerformanceExecutor2 getDefaultExecutor() {
|
||||||
return defaultExecutor;
|
return defaultExecutor;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
|
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
|
||||||
// ArrayBlockingQueue<>(10000);
|
// ArrayBlockingQueue<>(10000);
|
||||||
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
|
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||||
// packetQueue.add(object);
|
// packetQueue.add(object);
|
||||||
defaultExecutor.executeWithCongestionReport((state) -> {
|
switch(performanceStrategy) {
|
||||||
try {
|
case SINGLE_CORE:
|
||||||
long start=System.nanoTime();
|
runPacketSendTask0(sendTask, false);
|
||||||
IPv6Packet packet=sendTask.get();
|
break;
|
||||||
if(state) {
|
case MULTI_FILL:
|
||||||
packet.markCE();
|
case MULTI_SCATTER:
|
||||||
}
|
defaultExecutor.executeWithCongestionReport((state) -> {
|
||||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
runPacketSendTask0(sendTask, state);
|
||||||
long time=System.nanoTime()-start;
|
}, 20,performanceStrategy);
|
||||||
backplaneCount(time,state);
|
|
||||||
} catch (Exception e) {
|
break;
|
||||||
e.printStackTrace();
|
}
|
||||||
}
|
|
||||||
},20);
|
|
||||||
}
|
}
|
||||||
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
|
public void runPacketSendTask0(Supplier<IPv6Packet> sendTask,boolean ecn) {
|
||||||
try {
|
try {
|
||||||
boolean state=false;
|
|
||||||
long start=System.nanoTime();
|
long start=System.nanoTime();
|
||||||
IPv6Packet packet=sendTask.get();
|
IPv6Packet packet=sendTask.get();
|
||||||
if(state) {
|
if(ecn) {
|
||||||
packet.markCE();
|
packet.markCE();
|
||||||
}
|
}
|
||||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
||||||
long time=System.nanoTime()-start;
|
long time=System.nanoTime()-start;
|
||||||
backplaneCount(time,state);
|
backplaneCount(time,ecn);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||||
|
runPacketSendTask0(sendTask,false);
|
||||||
|
}
|
||||||
|
|
||||||
public void onReceive(IPv6NetworkLink link,IPv6Packet t){
|
public void onReceive(IPv6NetworkLink link,IPv6Packet t){
|
||||||
if (unduplicate(t)) {
|
if (unduplicate(t)) {
|
||||||
@@ -824,7 +845,7 @@ public class SRv6Router {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
},20);
|
},20,performanceStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enqueuePacket(IPv6Packet packet) {
|
public void enqueuePacket(IPv6Packet packet) {
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
package org.kne.concurrent;
|
package org.kne.concurrent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.locks.LockSupport;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.jctools.queues.MpscArrayQueue;
|
import org.jctools.queues.MpscArrayQueue;
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
|
||||||
public class HighPerformanceExecutor2 implements Executor {
|
public class HighPerformanceExecutor2 implements Executor {
|
||||||
|
|
||||||
@@ -33,6 +25,8 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
threads[i]=t;
|
threads[i]=t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ThreadElement implements Runnable{
|
private static class ThreadElement implements Runnable{
|
||||||
private MpscArrayQueue<Runnable> queue=new MpscArrayQueue<Runnable>(2048);
|
private MpscArrayQueue<Runnable> queue=new MpscArrayQueue<Runnable>(2048);
|
||||||
private AtomicInteger size=new AtomicInteger();
|
private AtomicInteger size=new AtomicInteger();
|
||||||
@@ -80,7 +74,7 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void execute(Runnable command) {
|
public void execute(Runnable command) {
|
||||||
if(execute0((x)->{command.run();},1000)) {
|
if(execute0((x)->{command.run();},1000, PerformanceStrategy.MULTI_FILL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
@@ -89,42 +83,47 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private long vl=0;
|
private long vl=0;
|
||||||
private boolean execute0(Consumer<Boolean> command,int limit) {
|
|
||||||
long ord=vl++;
|
private boolean execute0(Consumer<Boolean> command, int limit, PerformanceStrategy strategy) {
|
||||||
|
if(strategy.equals(PerformanceStrategy.MULTI_SCATTER)){
|
||||||
|
long ord=vl++;
|
||||||
ThreadElement te= threads[(int) (ord%threads.length)];
|
ThreadElement te= threads[(int) (ord%threads.length)];
|
||||||
boolean b=te.size()>limit;
|
boolean b=te.size()>limit;
|
||||||
return te.putTask( ()->{command.accept(b);});
|
return te.putTask( ()->{command.accept(b);});
|
||||||
}*/
|
}else {
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
|
ThreadElement te = threads[i];
|
||||||
|
boolean b = te.size() > limit;
|
||||||
|
if (!b) {
|
||||||
|
if (te.putTask(() -> {
|
||||||
|
command.accept(false);
|
||||||
|
})) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean execute0(Consumer<Boolean> command,int limit) {
|
for (int i = 0; i < threads.length; i++) {
|
||||||
for(int i=0;i<threads.length;i++) {
|
ThreadElement te = threads[i];
|
||||||
ThreadElement te= threads[i];
|
if (te.putTask(() -> {
|
||||||
boolean b=te.size()>limit;
|
command.accept(true);
|
||||||
if(!b) {
|
})) {
|
||||||
if(te.putTask( ()->{command.accept(false);})){
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i=0;i<threads.length;i++) {
|
|
||||||
ThreadElement te= threads[i];
|
|
||||||
if(te.putTask( ()->{command.accept(true);})){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeWithCongestionReport(Consumer<Boolean> command) {
|
public void executeWithCongestionReport(Consumer<Boolean> command) {
|
||||||
if(execute0(command,1000)) {
|
if(execute0(command,1000, PerformanceStrategy.MULTI_FILL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
}
|
}
|
||||||
public void executeWithCongestionReport(Consumer<Boolean> command,int limit) {
|
public void executeWithCongestionReport(Consumer<Boolean> command, int limit, PerformanceStrategy strategy) {
|
||||||
if(execute0(command,limit)) {
|
if(execute0(command,limit,strategy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.kne.concurrent;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 惰性计算器:只在输入变化且输出被需要时重算。
|
||||||
|
*
|
||||||
|
* 使用场景:路由表重算、统计信息聚合、UI 数据更新等
|
||||||
|
*
|
||||||
|
* @param <T> 输出类型
|
||||||
|
*/
|
||||||
|
public class LazyEvaluator<T> {
|
||||||
|
|
||||||
|
private final Supplier<T> recomputeFn; // 重算函数(可能较重)
|
||||||
|
private final AtomicBoolean dirty = new AtomicBoolean(true); // true表示需要重算
|
||||||
|
private volatile T cachedResult; // 缓存结果(volatile保证可见性)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recomputeFn 当真正需要重算时,调用此函数生成新值
|
||||||
|
*/
|
||||||
|
public LazyEvaluator(Supplier<T> recomputeFn) {
|
||||||
|
this.recomputeFn = recomputeFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记输入已变化(每次变化都调用)
|
||||||
|
* CAS 保证多线程下只有一个线程真正置脏,避免无谓的竞争
|
||||||
|
*/
|
||||||
|
public boolean markDirty() {
|
||||||
|
return dirty.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前值(如果 dirty 且被需要,则触发重算)
|
||||||
|
* 这个方法由需要输出的地方(如 UI 线程、路由引擎)调用
|
||||||
|
*/
|
||||||
|
public T get() {
|
||||||
|
if (dirty.get()) {
|
||||||
|
synchronized (this) {
|
||||||
|
// double-check:防止多个线程同时进入重算
|
||||||
|
if (dirty.get()) {
|
||||||
|
cachedResult = recomputeFn.get();
|
||||||
|
dirty.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前缓存的值(不触发重算)
|
||||||
|
* 适合那些“如果已经算好就直接用,否则也不强求”的场景
|
||||||
|
*/
|
||||||
|
public T getCached() {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否需要重算
|
||||||
|
*/
|
||||||
|
public boolean isDirty() {
|
||||||
|
return dirty.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user